Trying to move to a new view in swiftUI after a button press - xcode

I am trying to create an application which has the ability to create and view assignments. I have a page called AddAssignment.swift and ViewAssignment.swift, and I am trying to move from the AddAssignment page to the ViewAssignment page on a button press.
The user should enter the details in the text boxes, and then when the button is pressed, I want to save the information in the text box and move to the View Assignments screen. So far, I am not able to get the button to work correctly.
Here is my code:
import SwiftUI
struct AddAssignment: View {
// Properties
#State var taskName = ""
#State var dueDate = ""
#State var subject = ""
#State var weighting = ""
#State var totalMarks = ""
// Methods
func saveTask(a:String,b:String,c:String,d:String,e:String) -> [String] {
var newTask: [String] = []
newTask.append(a);
newTask.append(b);
newTask.append(c);
newTask.append(d);
newTask.append(e);
return newTask
}
// Main UI
var body: some View {
VStack {
Text("Add New Task")
.font(.headline)
.padding()
.scaleEffect(/*#START_MENU_TOKEN#*/2.0/*#END_MENU_TOKEN#*/)
Spacer()
Image(systemName: "plus.app.fill")
.foregroundColor(Color.orange)
.scaleEffect(/*#START_MENU_TOKEN#*/5.0/*#END_MENU_TOKEN#*/)
Spacer()
Group { // Text Fields
TextField("Enter assignment name", text: $taskName)
.padding([.top, .leading, .bottom])
TextField("Enter due date", text: $dueDate)
.padding([.top, .leading, .bottom])
TextField("Enter subject", text: $subject)
.padding([.top, .leading, .bottom])
TextField("Enter percent weighting", text: $weighting)
.padding([.top, .leading, .bottom])
TextField("Enter total marks", text: $totalMarks)
.padding([.top, .leading, .bottom])
}
Spacer()
Button("Create New Task", action: {
let task: [String] = [taskName, dueDate, subject, weighting, totalMarks]
print(task)
ViewAssignment()
})
Spacer()
}
}
}
struct AddAssignment_Previews: PreviewProvider {
static var previews: some View {
AddAssignment()
}
}
The console is able to return the values of the textbooks and store this information, I'm just not sure how to bring that across to another struct in the ViewAssignment.swift file.

to make the "move work", add this to "AddAssignment":
struct AddAssignment: View {
#State var toAssignment: Int? = nil
replace your Button("Create New Task", action: {...}, with
NavigationLink(destination: ViewAssignment(), tag: 1, selection: $toAssignment) {
Button("Create New Task") {
let task: [String] = [taskName, dueDate, subject, weighting, totalMarks]
print(task)
self.toAssignment = 1
}
}
make sure you wrap the whole thing in "NavigationView { ...}"

Related

Select the first item on a list and highlight it when item changes

I have a simple notes app that uses a list on the left and a texteditor on the right. The list has the titles of the notes, and the texteditor its text. When the user changes the text on the note, the list gets sorted to display the most current note (by date) on top, as its first item.
Started with Ventura, if I'm working on a note other than the first one, then that note (the item in the list) jumps to the top when the text is changed, however, if the first item in the list not visible (I'm working on a note that is way down), then when I change the text the item, it jumps to the top, but you don't jump with it. You are now in this state where you have to scroll up to get to the top and reselect the first item.
I tried using DispatchQueue.main.async to force to reselect the item onchange, but regardless of what I try, it doesn't scroll to the top, even when the selected note id is the correct one.
I ran out of ideas or things to try. How can I go back to the first item once the text is changed?
Here's the code:
struct NoteItem: Codable, Hashable, Identifiable {
let id: Int
var text: String
var date = Date()
var dateText: String {
dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return dateFormatter.string(from: date)
}
var tags: [String] = []
}
final class DataModel: ObservableObject {
#AppStorage("notes") public var notes: [NoteItem] = []
}
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var noteText: String = ""
#State var selectedNoteId: UUID?
var body: some View {
NavigationView {
List(data.notes) { note in
NavigationLink(
destination: NoteView(note: note),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
}
.navigationTitle("A title")
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: {
data.notes.append(NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []))
}) {
Image(systemName: "square.and.pencil")
}
}
}
.onAppear {
DispatchQueue.main.async {
selectedNoteId = data.notes.first?.id
}
}
.onChange(of: data.notes) { notes in
if selectedNoteId == nil || !notes.contains(where: { $0.id == selectedNoteId }) {
selectedNoteId = data.notes.first?.id
}
}
}
}
struct NoteView: View {
#EnvironmentObject private var data: DataModel
var note: NoteItem
#State var text: String = ""
var body: some View {
HStack {
VStack(alignment: .leading) {
TextEditor(text: $text).padding().font(.body)
.onChange(of: text, perform: { value in
guard let index = data.notes.firstIndex(of: note) else { return }
data.notes[index].text = value
data.sortList()
})
Spacer()
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}

Text gets truncated or hidden on a TextField on a macOS app

I have a TextField on a .sheet that is acting up and I don't know why or how to solve it. When you type a long text, the cursor reaches the end of the TextField (the right end), and it stays there. You can continue to write (and the text will get entered) but you can't see it, nor scroll to it, nor move the cursor.
Is this a default behavior? If not, how do I fix it? What am I not doing right? I haven't found anything either here at SO, or on Apple's forums or various SwiftUI sites.
Here's how it looks
And here's the code for the view:
struct SheetViewNew: View {
#EnvironmentObject private var taskdata: DataModel
#Binding var isVisible: Bool
#Binding var enteredText: String
#Binding var priority: Int
var priorities = [0, 1, 2]
var body: some View {
VStack {
Text("Enter a new task")
.font(.headline)
.multilineTextAlignment(.center)
TextField("New task", text: $enteredText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 14.0))
.onSubmit{
if enteredText == "" {
self.isVisible = false
return
}
taskdata.tasks.append(Task(id: UUID(), title: enteredText, priority: priority))
}
Picker("Priority", selection: $priority) {
Text("Today").tag(0)
Text("Important").tag(1)
Text("Normal").tag(2)
}.pickerStyle(RadioGroupPickerStyle())
Spacer()
HStack {
Button("Cancel") {
self.isVisible = false
self.enteredText = ""
}
.keyboardShortcut(.cancelAction)
.buttonStyle(DefaultButtonStyle())
Spacer()
Button("Add Task") {
if enteredText == "" {
self.isVisible = false
return
}
taskdata.tasks.append(Task(id: UUID(), title: enteredText, priority: priority))
taskdata.sortList()
self.isVisible = false
}
.keyboardShortcut(.defaultAction)
.buttonStyle(DefaultButtonStyle())
}
}
.frame(width: 300, height: 200)
.padding()
}
}

Select the first item on a list and highlight it when the application launches

When the app first launches in macOS (Big Sur), it populates a list with the items saved by the user. When the user clicks on an item on that list, a second view opens up displaying the contents of that item.
Is there a way to select the first item on that list, as if the user clicked it, and display the second view when the app launches? Furthermore, if I delete an item on the list, I can't go back and select the first item on the list and displaying the second view for that item, or if I create new item, same applies, can't select it.
I have tried looking at answers here, like this, and this, and looked and tried code from a variety of places, but I can't get this to work.
So, using the code answered on my previous question, here's how the bare bones app looks like:
struct NoteItem: Codable, Hashable, Identifiable {
let id: Int
var text: String
var date = Date()
var dateText: String {
dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return dateFormatter.string(from: date)
}
var tags: [String] = []
}
final class DataModel: ObservableObject {
#AppStorage("notes") public var notes: [NoteItem] = []
}
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var noteText: String = ""
var body: some View {
NavigationView {
List(data.notes) { note in
NavigationLink(destination: NoteView(note: note)) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
Text("Select a note...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.navigationTitle("A title")
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: {
data.notes.append(NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []))
}) {
Image(systemName: "square.and.pencil")
}
}
}
}
}
struct NoteView: View {
#EnvironmentObject private var data: DataModel
var note: NoteItem
#State var text: String = ""
var body: some View {
HStack {
VStack(alignment: .leading) {
TextEditor(text: $text).padding().font(.body)
.onChange(of: text, perform: { value in
guard let index = data.notes.firstIndex(of: note) else { return }
data.notes[index].text = value
})
Spacer()
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
.onAppear() {
print(data.notes.count)
}
}
}
I have tried adding #State var selection: Int? in AllNotes and then changing the list to
List(data.notes, selection: $selection)
and trying with that, but I can't get it to select anything.
Sorry, newbie here on SwiftUI and trying to learn.
Thank you!
You were close. Table view with selection is more about selecting item inside table view, but you need to select NavigationLink to be opened
There's an other initializer to which does exactly what you need. To selection you pass current selected item. To tag you pass current list item, if it's the same as selection, NavigationLink will open
Also you need to store selectedNoteId instead of selectedNote, because this value wouldn't change after your update note properties
Here I'm setting selectedNoteId to first item in onAppear. You had to use DispatchQueue.main.async hack here, probably a NavigationLink bug
To track items when they get removed you can use onChange modifier, this will be called each time passed value is not the same as in previous render
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var noteText: String = ""
#State var selectedNoteId: UUID?
var body: some View {
NavigationView {
List(data.notes) { note in
NavigationLink(
destination: NoteView(note: note),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
}
.navigationTitle("A title")
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: {
data.notes.append(NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []))
}) {
Image(systemName: "square.and.pencil")
}
}
}
.onAppear {
DispatchQueue.main.async {
selectedNoteId = data.notes.first?.id
}
}
.onChange(of: data.notes) { notes in
if selectedNoteId == nil || !notes.contains(where: { $0.id == selectedNoteId }) {
selectedNoteId = data.notes.first?.id
}
}
}
}
Not sure what's with #AppStorage("notes"), it shouldn't work because this annotation only applied to simple types. If you wanna store your items in user defaults you had to do it by hand.
After removing it, you were missing #Published, that's why it wasn't updating in my case. If AppStorage could work, it may work without #Published
final class DataModel: ObservableObject {
#Published
public var notes: [NoteItem] = [
NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []),
NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []),
NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []),
NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []),
]
}

SwiftUI - Using a Picker on another view to sort results from API call

I have a view that makes an API call to pull nearby restaurant data and display it in a list. I've placed a button in the navigation bar of that view to display a sheet which will ultimately show the user filter options. For now, it uses a Picker placed in a form to allow the user to pick how they want to sort the results. I am using a variable with a #Binding property wrapper in the filter view to pass the selected value to a #State variable in the restaurant data view (selectedOption). It all works great until the view is reloaded. Either by going to a different view or relaunching the app. It appears the selectedOption variable in my API call in the onAppear function of the restaurant data view is being reset to what I've set the default value to when I defined the #State variable. I am wondering if there is a way to persist the value of what was chosen in the filter view through view reloads of the restaurant data view.
Restaurant data view:
import SwiftUI
import SwiftyJSON
import SDWebImageSwiftUI
struct RestaurantsView: View {
#EnvironmentObject var locationViewModel: LocationViewModel
#EnvironmentObject var venueDataViewModel: VenueDataViewModel
#State var selectedOption: String = "rating"
#State private var showingSheet = false
#State private var searchText = ""
#State private var showCancelButton: Bool = false
var body: some View {
let CPLatitude: Double = locationViewModel.lastSeenLocation?.coordinate.latitude ?? 0.00
let CPLongitude: Double = locationViewModel.lastSeenLocation?.coordinate.longitude ?? 0.00
GeometryReader { geometry in
VStack {
Text("Restaurants")
.padding()
List(venueDataViewModel.venuesListTen) { index in
NavigationLink(destination: RestaurantsDetailView(venue: index)) {
HStack {
VStack(alignment: .leading, spacing: 6) {
Text(index.name ?? "")
.font(.body)
.lineLimit(2)
Text(index.address ?? "")
.font(.subheadline)
.lineLimit(2)
}
}
}
Spacer()
if index.image != nil {
WebImage(url: URL(string: index.image ?? ""), options: .highPriority, context: nil)
.resizable()
.frame(width: 70, height: 70, alignment: .center)
.aspectRatio(contentMode: .fill)
.cornerRadius(12)
}
}
Text("Selected: \(selectedOption)")
Spacer()
}.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
showingSheet.toggle()
}, label: {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
)
.sheet(isPresented: $showingSheet, content: {
NavigationView {
RestaurantsFilterView(selectedOption: self.$selectedOption)
}
})
}
}
.onAppear {
venueDataViewModel.retrieveVenues(latitude: CPLatitude, longitude: CPLongitude, category: "restaurants", limit: 50, sortBy: selectedOption, locale: "en_US") { (response, error) in
if let error = error {
print("\(error)")
}
}
}
}
}
}
Filter view:
import SwiftUI
struct RestaurantsFilterView: View {
#EnvironmentObject var locationViewModel: LocationViewModel
#EnvironmentObject var venueDataViewModel: VenueDataViewModel
var sortOptions = ["rating", "review_count"]
#Binding var selectedOption: String
var body: some View {
let CPLatitude: Double = locationViewModel.lastSeenLocation?.coordinate.latitude ?? 0.00
let CPLongitude: Double = locationViewModel.lastSeenLocation?.coordinate.longitude ?? 0.00
VStack {
Text("Filter")
Form {
Section {
Picker(selection: $selectedOption, label: Text("Sort")) {
ForEach(sortOptions, id: \.self) {
Text($0)
}
}.onChange(of: selectedOption) { Equatable in
venueDataViewModel.retrieveVenues(latitude: CPLatitude, longitude: CPLongitude, category: "restaurants", limit: 50, sortBy: selectedOption, locale: "en_US") { (response, error) in
if let error = error {
print("\(error)")
}
}
}
}
}
}
}
}
I am still new to Swift and SwiftUI so I appreciate the help.
Thank you!

SwiftUI onTapGesture displays new view

I am using SwiftUI to make my own Pizza delivery application. I am now stuck cause I do not know how I could display the same image view (including a pizza's description) related to his own index from the array. Instead of the lines {self.images print("ok")}, I would like that each time the user tap on an image from the array it displays the same image with a short text description. Is the description of my issue clear enough? I would be extremely grateful if anyone could help me out with this. Thanks a lot for reading it. This is the code:
HStack {
ForEach(images, id: \.id) { post in
ForEach(0..<1) { _ in
ImageView(postImages: post)
}
}
Spacer()
}
.onTapGesture {
self.images
print("ok")
}
.navigationBarTitle("Choose your Pizza")
.padding()
Assuming you have a struct for your Item, you need to conform it to Identifiable:
struct Item: Identifiable {
var id: String { name } // needed for `Identifiable`
let name: String
let imageName: String
let description: String
}
Then in your main view:
struct ContentView: View {
let items = [
Item(name: "item1", imageName: "circle", description: "some description of item 1"),
Item(name: "item2", imageName: "circle", description: "some description of item 2"),
Item(name: "item3", imageName: "circle", description: "some description of item 3"),
]
#State var selectedItem: Item? // <- track the selected item
var body: some View {
NavigationView {
HStack {
ForEach(items, id: \.id) { item in
ImageView(imageName: item.imageName)
.onTapGesture {
self.selectedItem = item // select the tapped item
}
}
Spacer()
}
.navigationBarTitle("Choose your Pizza")
.padding()
}
.sheet(item: $selectedItem) { item in // show a new sheet if selectedItem is not `nil`
DetailView(item: item)
}
}
}
If you have a custom view for your image:
struct ImageView: View {
let imageName: String
var body: some View {
Image(systemName: imageName)
}
}
you can create a detailed view for your item (with item description etc):
struct DetailView: View {
let item: Item
var body: some View {
VStack {
Text(item.name)
Image(systemName: item.imageName)
Text(item.description)
}
}
}
EDIT
Here is a different approach using the same View to display an image or an image with its description:
struct ContentView: View {
#State var items = [
Item(name: "item1", imageName: "circle", description: "some description of item 1"),
Item(name: "item2", imageName: "circle", description: "some description of item 2"),
Item(name: "item3", imageName: "circle", description: "some description of item 3"),
]
var body: some View {
NavigationView {
HStack {
ForEach(items, id: \.self) { item in
DetailView(item: item)
}
Spacer()
}
.navigationBarTitle("Choose your Pizza")
.padding()
}
}
}
struct DetailView: View {
#State var showDescription = false
let item: Item
var body: some View {
VStack {
Text(item.name)
Image(systemName: item.imageName)
if showDescription {
Text(item.description)
}
}
.onTapGesture {
self.showDescription.toggle()
}
}
}
and conform Item to Hashable:
struct Item: Hashable
or instead of:
ForEach(items, id: \.self)
specify the id explicitly:
ForEach(items, id: \.name)
I also took a stab at it, for what it's worth.
(BTW #pawello2222's answer is also 100% correct. There is more than one way to achieve what you described)
I first created a pizza menu (using placeholder images):
Main pizza list view
Upon selecting an item from the menu, you are taken to the chosen pizza detail:
Chosen pizza detail view
I'm using NavigationView and NavigationLink to navigate between views.
Here's the full source code (for best results run in Swift Playgrounds):
import PlaygroundSupport // For running in Swift Playground only
import SwiftUI
// The main pizza listing page (view)
struct PizzaList: View {
// Array of pizza images
let pizzaImages = [
Image(systemName: "circle.fill"),
Image(systemName: "square.fill"),
Image(systemName: "triangle.fill")
]
// Array of pizza descriptions
let pizzaDescriptions = [
"Circular Pizza",
"Square Pizza",
"Triangle Pizza"
]
var body: some View {
// Wrap in navigation view to be able to switch between "pages"
NavigationView {
VStack {
ForEach(0 ..< pizzaImages.count) { index in
// Wrap in navigation link to provide a link to the next "page"
NavigationLink(
// This is the contents of the chosen pizza page
destination: PizzaDetail(
pizzaImage: self.pizzaImages[index],
description: self.pizzaDescriptions[index]
)
) {
// This is an item on the main list
PizzaRow(
pizzaImage: self.pizzaImages[index],
description: self.pizzaDescriptions[index]
)
}
}
}
.navigationBarTitle("Available Pizzas")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
// Layout for each pizza item on the list
struct PizzaRow: View {
let pizzaImage: Image
let description: String
var body: some View {
HStack {
Spacer()
pizzaImage
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.orange)
Spacer()
Text(self.description)
Spacer()
}
.padding()
}
}
// Layout for the chosen pizza page
struct PizzaDetail: View {
let pizzaImage: Image
let description: String
var body: some View {
VStack {
Spacer()
Text(description)
.font(.title)
Spacer()
pizzaImage
.resizable()
.aspectRatio(contentMode: .fit)
Spacer()
}
.padding()
.navigationBarTitle("Your Pizza Choice")
}
}
// For Playgrounds only
PlaygroundPage.current.setLiveView(PizzaList())

Resources