This question already has answers here:
SwiftUI #State var initialization issue
(8 answers)
Closed 9 months ago.
I have created a very simple test in SwiftUI:
Pokemon
struct Pokemon: Identifiable {
let id: Int
let name: String
}
PokemonList
struct PokemonList: View {
#State private var pokemonList: [Pokemon] = []
var body: some View {
List(pokemonList) {
PokemonRow(pokemon: $0)
}
.listStyle(PlainListStyle())
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Here I fill Pokemon list from API
}
}
}
init(_ pokemonList: [Pokemon] = []) {
self.pokemonList = pokemonList
}
}
#if DEBUG
struct PokemonList_Previews: PreviewProvider {
static var previews: some View {
PokemonList([
Pokemon(id: 1, name: "Bulbasaur"),
Pokemon(id: 25, name: "Pikachu"),
Pokemon(id: 150, name: "Mewtwo")
])
}
}
#endif
PokemonRow
struct PokemonRow: View {
var pokemon: Pokemon
var body: some View {
HStack(alignment: .center) {
Text("#\(pokemon.id)")
Text(pokemon.name)
}
.padding()
.frame(height: 50)
}
}
#if DEBUG
struct PokemonRow_Previews: PreviewProvider {
static var previews: some View {
PokemonRow(pokemon: Pokemon(id: 25, name: "Pikachu"))
}
}
#endif
The PokemonRow preview works fine, and the API call too (the list is correctly filled after the view appears), but for some reason the PokemonList preview always displays empty content. Why is that?
Thank you for your help
init(_ pokemonList: [Pokemon] = []) {
self._pokemonList = State(initialValue: pokemonList) // << #State init
}
Related
I wanted to show a list of tags that can be selectable and want to make sure only one tag is selected in the list.
I tried to use #State, #ObservedObject, and #Published, but no luck. What is the best solution here?
import SwiftUI
struct Tag: Identifiable {
let id: Int
let name: String
var selected: Bool = false
}
struct ContentView: View {
#State var tags: [Tag]
var body: some View {
VStack {
ForEach(tags) { tag in
TagView(name: tag.name, isSelected: tag.selected) {
for (index, _) in tags.enumerated() {
// select only one tag
tags[index].selected = tags[index].id == tag.id
}
}
}
}
}
}
struct TagView: View {
let name: String
#State var isSelected: Bool
var onAction: (() -> Void) = { }
var body: some View {
Button(action: onAction) {
Text(name)
.foregroundColor(.white)
.padding()
.background(
isSelected ? Color.red : Color.blue
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(tags: [
Tag(id: 1, name: "Swift"),
Tag(id: 2, name: "Xcode"),
Tag(id: 3, name: "Apple")
])
}
}
Removing #State of isSelected variable in TagView resolved the issue
I'm not sure if I created my custom TextField properly, because I am unable to observe the value changes to an #Binded text. Running the following code, you may observe that print(text) is not executed when you manually enter text into the text field.
import SwiftUI
#main
struct TestOutWeirdTextFieldApp: App {
#State var text: String = "" {
didSet {
print(text)
}
}
var body: some Scene {
WindowGroup {
StandardTextField(placeholderText: "Enter text", defaultText: $text)
}
}
}
struct StandardTextField: View {
#State var placeholderText: String {
didSet {
print(#line)
print(placeholderText)
}
}
#Binding var defaultText: String {
didSet {
print(#line)
print(defaultText)
}
}
#State var systemImage: String?
#State var underlineColor: Color = .accentColor
#State var edges: Edge.Set = .all
#State var length: CGFloat? = nil
#State var secure: Bool = false
var body: some View {
HStack {
if secure {
SecureField(placeholderText, text: $defaultText)
.foregroundColor(underlineColor)
} else {
TextField(placeholderText, text: $defaultText)
.foregroundColor(underlineColor)
}
if let systemImage = systemImage {
Image(systemName: systemImage)
.foregroundColor(.white)
}
}
.overlay(
Rectangle()
.frame(height: 2)
.padding(.top, 35)
)
.foregroundColor(underlineColor)
.padding(edges, length)
}
}
struct StandardTextView_Previews: PreviewProvider {
static var previews: some View {
StandardTextField(placeholderText: "Placement text", defaultText: .constant("")).previewLayout(.sizeThatFits)
}
}
Instead of didSet you need to use .onChange(of: modifier, like
HStack {
// ... your code here
}
.onChange(of: defaultText) { print($0) } // << this !!
.overlay(
How can I save the value of the stepper in the UserDefaults every time I press + or -?
I don't want a separate save button.
struct Settings: View {
#State private var zielwert = UserDefaults.standard.integer(forKey: "zielwert")
var body: some View {
VStack {
Stepper("Zielwert \(zielwert)", value: $zielwert, in: 90...160)
Button("Speichern") {
UserDefaults.standard.set(self.zielwert, forKey: "zielwert")
}
}
.padding(.horizontal, 23.0)
}
}
SwiftUI 2
Use AppStorage:
struct Settings: View {
#AppStorage("zielwert") private var zielwert = 90
var body: some View {
VStack {
Stepper("Zielwert \(zielwert)", value: $zielwert, in: 90...160)
}
.padding(.horizontal, 23.0)
}
}
SwiftUI 1
Use .onReceive:
import Combine
struct Settings: View {
#State private var zielwert = UserDefaults.standard.integer(forKey: "zielwert")
var body: some View {
VStack {
Stepper("Zielwert \(zielwert)", value: $zielwert, in: 90...160)
.onReceive(Just(zielwert)) { zielwert in
print(zielwert)
UserDefaults.standard.set(zielwert, forKey: "zielwert")
}
}
.padding(.horizontal, 23.0)
}
}
i am sure i am making something wrong, but i don't know what.
I have a small list and i update texts with a timer which i can see in the debugger which updates. But the list won't be updated...
Thank you for help.
struct ListTest: View {
#State var texts : [String]
var body: some View {
List {
ForEach(self.texts, id: \.self) { text in
Text(text)
}
}
}
}
struct ContentView: View {
#State var texts = ["a"]
var body: some View {
ListTest(texts: self.texts)
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { (timer) in
self.texts.append("\(self.texts.count)")
print(self.texts)
}
}
}
}
remove #State property wrapper from your ListTest
struct ListTest: View {
var texts : [String]
var body: some View {
List {
ForEach(self.texts, id: \.self) { text in
Text(text)
}
}
}
}
Finally, think about more "combine compatible" code
import SwiftUI
struct ListTest: View {
var texts: [String]
var body: some View {
List {
ForEach(self.texts, id: \.self) { text in
Text(text)
}
}
}
}
struct ContentView: View {
#State var texts = ["a"]
let tp = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
var body: some View {
ListTest(texts: self.texts)
.onReceive(tp) { (date) in
self.texts.append("\(self.texts.count)")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
which is functionally equal
I'm trying to present a modal within a List. Modals have changed many times in the betas, but I believe I'm using the correct/latest way (as of Xcode Beta 7). However, when I'm triggering the modal from within a list, for each list item it opens once but then never again.
import SwiftUI
//This works perfectly fine
struct BasicTest : View {
struct SampleObject: Identifiable {
var id = UUID()
var name: String
}
let sampleObjects = [
SampleObject(name: "Buffalo"),
SampleObject(name: "Italy"),
SampleObject(name: "Portland"),
]
#State private var showModal:Bool = false
var body: some View {
Button(action: {
print("Modal to open")
self.showModal = true
}) {
Text("Show Modal")
}.sheet(isPresented: self.$showModal)
{
TestDetailView(name: "Example")
}
}
}
#if DEBUG
struct BasicTest_Previews: PreviewProvider {
static var previews: some View {
BasicTest()
}
}
#endif
//this one only opens once
struct ListTest : View {
struct SampleObject: Identifiable {
var id = UUID()
var name: String
}
let sampleObjects = [
SampleObject(name: "Buffalo"),
SampleObject(name: "Italy"),
SampleObject(name: "Portland"),
]
#State private var showModal:Bool = false
var body: some View {
List {
ForEach(sampleObjects) {
item in
Button(action: {
print("Modal to open")
self.showModal = true
}) {
Text("Show Modal for \(item.name)")
}.sheet(isPresented: self.$showModal)
{
TestDetailView(name: item.name)
}
}
}
}
}
#if DEBUG
struct ListTest_Previews: PreviewProvider {
static var previews: some View {
ListTest()
}
}
#endif
struct TestDetailView: View {
#Environment(\.presentationMode) var presentationMode
var name: String
var body: some View {
VStack {
Button(action: {
print("Button clicked")
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.compact.down").font(Font.system(.largeTitle).bold())
}
Text(name)
}
}
}
No error messages exist that I can see, just a flash when trying to open a second time.
It’ll work if you create a view for list row and move .sheet() there