Update an ListView SwiftUI - swiftui

I have an View which contains an List-View and an Button. When the Button is pressed there will be added an new Item to an array. The List View displays all of the items in this array. But If I press the button, the List-View dont display the changes in the array.
Here is my Code:
import SwiftUI
var myArray = ["Harald", "Jürgen", "Günter"]
struct ContentView: View {
var body: some View {
HStack{
List(myArray, id: \.self) {
Name in
Text(Name)
}
Button(action: {
myArray.append("Harald")
print(myArray)
}, label: {
Text("Button")
})
}
}
}
My question is now how I can do it, that my List View will display new Items / changes as well.
Thanks, Boothosh

myArray needs to be an #State and it needs to be inside the ContentView.
I recommend you to read and watch some SwiftUI tutorials.
struct ContentView: View {
#State var myArray = ["Harald", "Jürgen", "Günter"]
var body: some View {
HStack{
List(myArray, id: \.self) {
Name in
Text(Name)
}
Button(action: {
myArray.append("Harald")
print(myArray)
}, label: {
Text("Button")
})
}
}
}

Related

With SwiftUI 3.0 .swipeActions in a ForEach how do you have an action go to another view while passing that view the input argument of the ForEach?

I am tying to add a .swipeAction to a ForEach list in which I want to pass the element in the list that was selected by the user to another invoked View. In other words when the user swipes on an item in the list, I want the user to be taken to a new View which has the contents of that item in the list so that it can display details from that item in that new view.
With that said, I have mocked up this simple example which I hope helps show the issue I am having.
import SwiftUI
struct ContentView: View {
var colors : [Color] = [Color.red, Color.green,
Color.blue, Color.yellow,
Color.brown, Color.cyan, Color.pink]
var body: some View {
NavigationView {
VStack {
List {
ForEach(colors, id: \.self) { color in
ColorRowView(color: color)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(action: { print("Hello From The First Button") },
label: {Label("Hello", systemImage: "face.smiling")})
.tint(Color.orange)
NavigationLink(destination: ColorShowView(color: color),
label: {Image(systemName: "magnifyingglass")})
.tint(.yellow)
}
}
}
Spacer()
NavigationLink(destination: ColorShowView(color: Color.red),
label: { Image(systemName: "magnifyingglass" ) } ).tint(.yellow)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Text("List Of Colors"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ColorRowView: View {
var color: Color
var body: some View {
VStack {
Text("Color \(color.description)").foregroundColor(color)
}
}
}
struct ColorShowView: View {
var color: Color
var body: some View {
VStack {
Text("Color Name \(color.description)").foregroundColor(color)
Text("Color Hash Value \(color.hashValue)").foregroundColor(color)
}
}
}
What I find is that if I put a NavigationLink as a button on the .swipeActions it shows up correctly, but when tapped the NavigationLink does not execute and hence does not take you to a new View.
If I move that same Navigation Link down to after the .swipeActions, and invoke it with some #State it works, but it adds another row in-between each row in the list of the ForEach. In other words, the ForEach of course sees it as part of its list and adds it in with the other items in the list. Even if I add a .hidden() onto that NavigationLink, it still takes up space with a new row, it just hides the contents of the row, not the row itself.
If I move the NavigationLink outside of the ForEach, then the input argument of color from the ForEach is out of scope. It will correctly build the view and execute the link (using an action and some #State), but it can not pass the color input from the ForEach because of course it is out of scope. If you hard code a color in its place it works fine, except of course for the fact that it does not have the color from the users selection from the list.
Note I put a simple NavigationLink on the bottom of the view as well just so that I could see that it worked correctly outside of the issue with the .swipeActions, and it does work fine with a hard coded color value like Color.red.
This is of course a very made up example, but I think it does show the issue.
Has anyone used a .swipeActions to invoke a NavigationLink to a new view passing into that view the users selected item (in this case the color)? If so how do you get that to work. It feels like a chicken and the egg problem, I can not seem to both have access to the scope in which the input data (the color) is available, and a NavigationLink that does not become part of the view of the ForEach list.
Anyone have any ideas? Thanks in advance for any and all commentary, corrections, ideas, etc.
First solution: by using fullScreenCover and #State var selectedColor
#Environment(.presentationMode) var presentationMode
// ContentView.swift
// StackOverFlow
//
// Created by Mustafa T Mohammed on 12/31/21.
//
import SwiftUI
struct ContentView: View {
#State var isColorShowViewPresented = false
#State var selectedColor: Color = .yellow
var colors : [Color] = [Color.red, Color.green,
Color.blue, Color.yellow,
Color.brown, Color.cyan, Color.pink]
var body: some View {
NavigationView {
VStack {
// you can use List instead of ForEach loop it's the same
// less code :)
List(colors, id: \.self) { color in
ColorRowView(color: color)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(action: {
isColorShowViewPresented.toggle() // toggle isColorShowViewPresented to trigger the
// fullScreenCover
selectedColor = color
print("Hello From The First Button")
},
label: {Label("Hello", systemImage: "face.smiling")})
.tint(Color.orange)
}
}
Spacer()
.fullScreenCover(isPresented: $isColorRowViewPresented) {
// if you like to implement what happen when user dismiss the presented view
print("user dissmissed ColorRowView")
} content: {
ColorShowView(color: selectedColor) // view that you want to present
}
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Text("List Of Colors"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ColorRowView: View {
var color: Color
var body: some View {
VStack {
Text("Color \(color.description)").foregroundColor(color)
}
}
}
struct ColorShowView: View {
#Environment(\.presentationMode) var presentationMode
var color: Color
var body: some View {
VStack {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
Spacer()
Text("Color Name \(color.description)").foregroundColor(color)
Text("Color Hash Value \(color.hashValue)").foregroundColor(color)
Spacer()
}
}
}
Second solution: NavigationLink and #State var selectedColor
//
// ContentView.swift
// StackOverFlow
//
// Created by Mustafa T Mohammed on 12/31/21.
//
import SwiftUI
struct ContentView: View {
#State var isColorShowViewPresented = false
#State var selectedColor: Color = .yellow
var colors : [Color] = [Color.red, Color.green,
Color.blue, Color.yellow,
Color.brown, Color.cyan, Color.pink]
var body: some View {
NavigationView {
VStack {
// you can use List instead of ForEach loop it's the same
// less code :)
List(colors, id: \.self) { color in
ColorRowView(color: color)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(action: {
isColorShowViewPresented.toggle() // toggle isColorShowViewPresented to trigger the
// NavigationLink
selectedColor = color
print("Hello From The First Button")
},
label: {Label("Hello", systemImage: "face.smiling")})
.tint(Color.orange)
}
}
Spacer()
NavigationLink("", isActive: $isColorShowViewPresented) {
ColorShowView(color: selectedColor)
}
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Text("List Of Colors"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ColorRowView: View {
var color: Color
var body: some View {
VStack {
Text("Color \(color.description)").foregroundColor(color)
}
}
}
struct ColorShowView: View {
var color: Color
var body: some View {
VStack {
Text("Color Name \(color.description)").foregroundColor(color)
Text("Color Hash Value \(color.hashValue)").foregroundColor(color)
}
}
}

Why watchOS picker always shows "ScrollView contentOffset binding has been read" warning?

Bare minimum picker test app for watchOS.
import SwiftUI
#main
struct PickerTestApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
}
}
ContentView
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
NavigationLink(destination: DistanceSelectView()) {
Text("Next screen")
}
}
}
}
DistanceSelectView
import SwiftUI
struct DistanceSelectView: View {
#State var Age = 1
var body: some View {
VStack {
Picker(selection: $Age, label: Text("Select your age.[\(Age)]")) {
ForEach(10 ..< 100, id: \.self) { num in
Text("\(num)")
}
}
}
}
}
When run, and "Next screen" NavigationLink is pressed, it always displays the following warning:
"ScrollView contentOffset binding has been read; this will cause grossly inefficient view performance as the ScrollView's content will be updated whenever its contentOffset changes. Read the contentOffset binding in a view that is not parented between the creator of the binding and the ScrollView to avoid this."
What I'm doing wrong here?

SwiftUI list does not scroll using 2-finger swipe on trackpad

I have been testing this simple SwiftUI code: It has a list in ContentView. When a cell of the list is tapped, it pushes a SecondList into the navigation stack. When this SwiftUI code was run on a real iPad with a trackpad, I used 2-finger swipe to scroll the list up and down. The list in ContentView scrolls smoothly. However, for SecondList, it may freeze randomly and thereafter not responding.
It also failed in a UIKit project, where I replaced a UINavigationView with this ContentView using UIHostingViewController.
It was testing it with iPadOS 14.5 on both Simulator and iPad Pro hardware with Magic Keyboard.
How do I fix this? Thank you.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<30, id: \.self) { index in
NavigationLink(
destination: SecondList(),
label: {
Text(String(index))
})
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondList: View {
var body: some View {
List {
ForEach(0..<30, id: \.self) { index in
Text(String(index))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Use a scrollView
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<30, id: \.self) { index in
NavigationLink(
destination: SecondList(),
label: {
Text(String(index))
})
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondList: View {
var body: some View {
scrollView {
List {
ForEach(0..<30, id: \.self) { index in
Text(String(index))
}
}
}
}
}

SWIFTUI Button or NavigationLink?

I have a button called "save" that saves the user inputs.
But, I want to make it like, if the user tap on Button "Save", then the screen automatically goes back to the previous view. Can I do that by just adding a code to an action in Button? or do I have to use NavigationLink instead of Button?
Button(action: {
let title = shortcutTitle
currentShortcutTitle = title
UserDefaults.standard.set(title, forKey: "title")
}, label: {
Text("Save")
.padding()
.frame(width: 120, height: 80)
.border(Color.black)
}) //: Button - save
If you're just trying to go back to the previous view and already inside a NavigationView stack, you can use #Environment(\.presentationMode):
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Screen2()) {
Text("Go to screen 2")
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct Screen2 : View {
#Environment(\.presentationMode) var presentationMode //<-- Here
var body: some View {
Button("Dismiss") {
presentationMode.wrappedValue.dismiss() //<-- Here
}
}
}

SwiftUI dismiss modal sheet presented from NavigationView (Xcode Beta 5)

I am attempting to dismiss a modal view presented via a .sheet in SwiftUI - called by a Button which is within a NavigationViews navigationBarItems, as per below:
struct ModalView : View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.presentationMode.value.dismiss()
}, label: { Text("Save")})
}
}
struct ContentView : View {
#State var showModal: Bool = false
var body: some View {
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button(action: {
self.showModal = true
}, label: { Text("Add") })
.sheet(isPresented: $showModal, content: { ModalView() })
)
}
}
}
The modal does not dismiss when the Save button is tapped, it just remains on screen. The only way to get rid of it is swiping down on the modal.
Printing the value of self.presentationMode.value always shows false so it seems to think that it hasn't been presented.
This only happens when it is presented from the NavigationView. Take that out and it works fine.
Am I missing something here, or is this a beta issue?
You need to move the .sheet outside the Button.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") {
self.showModal = true
}
)
.sheet(isPresented: $showModal, content: { ModalView() })
}
You can even move it outside the NavigationView closure.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") { self.showModal = true }
)
}
.sheet(isPresented: $showModal, content: { ModalView() })
Notice you can also simplify the Button call if you have a simple text button.
The solution is not readily apparent in the documentation and most tutorials opt for simple solutions. But I really wanted a button in the NavigationBar of the sheet that would dismiss the sheet. Here is the solution in six steps:
Set the DetailView to not show.
Add a button to set the DetailView to show.
Call the .sheet(isPresented modifier to display the sheet.
Wrap the view that will appear in the sheet in a NavigationView because we want to display a .navigationBarItem button.
PresentationMode is required to dismiss the sheet view.
Add a button to the NavBar and call the dismiss method.
import SwiftUI
struct ContentView: View {
// 1
#State private var showingDetail = false
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Show Detail") {
showingDetail = true // 2
}
// 3
.sheet(isPresented: $showingDetail) {
// 4
NavigationView {
DetailView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DetailView: View {
// 5
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Detail View!")
// 6
.navigationBarItems(leading: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "x.circle")
.font(.headline)
.foregroundColor(.accentColor)
})
}
}