SwiftUI - LazyVStack displaying more elements in view than required - swiftui

I've been following a youtube tutorial for using LazyVStacks.
https://www.youtube.com/watch?v=o6D7mUXjSmI
When I run the same code as per the tutorial, the LazyVStack using Xcode Version 13.2.1 (13C100) on a iPhone 11 Pro Max (running iOS 15.2) prints out 83 statements, when only 42 rows are in view. As per the tutorial it should only print 42 statements to the console.
Is this an Xcode/iOS bug? I'm unable to download the latest Xcode as my Mac doesn't support macOS 12.0 to verify.
Video tutorial showing on 42 print statements in the console on iPhone 11 Pro Max:
My code showing 83 print statements in the console on iPhone 11 Pro Max
import SwiftUI
struct SampleRow: View {
let id: Int
var body: some View {
Text("Row \(id)")
}
init(id: Int) {
print("Loading row \(id)")
self.id = id
}
}
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack {
ForEach(1...1000, id: \.self, content: SampleRow.init)
}
}
}
}

I got the exact same problem, I've got several views that render and fetch data dynamically upon reaching the bottom of the page but for some unknown reason, the same exact code is not working on a view where all the data are rendered at once.. I don't know if it's a bug in LazyVStack implementation or anything else but the behavior is ambiguous

It's working fine in Xcode 13. I'm not sure why you need a LazyVStack, unless you're using some sort of grid. I slightly modified the code based on there's no much use for it here. It's printing all 1000.
import SwiftUI
struct SampleRow: View {
let id: Int
var body: some View {
Text("Row \(id)")
}
init(id: Int) {
print("Loading row \(id)")
self.id = id
}
}
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(1...1000, id: \.self) { index in
SampleRow(id: index)
}
}
}
}

Related

AsyncImage in List is broken on iOS 16

struct Item: Identifiable {
let id: String
let url = URL(string: "https://styles.redditmedia.com/t5_j6lc8/styles/communityIcon_9uopq0bazux01.jpg")!
}
struct Content: View {
let model: [Item] = {
var model = [Item]()
for i in 0 ..< 100 {
model.append(.init(id: String(i)))
}
return model
}()
var body: some View {
List(model) { item in
Row(item: item)
}
}
}
struct Row: View {
let item: Item
var body: some View {
AsyncImage(url: item.url)
}
}
Running code above with Xcode 14.1 on iOS 16.1 simulator, AsyncImage sometimes doesn’t properly show downloaded image but grey rectangle instead when scrolling in list. Is this bug or am I missing something? Thanks
My solution was to use VStack in ScrollView instead of List. It looks like it's working and it doesn't have any other drawbacks.

Vertical alignment of HStack not working and not intuitive

I have this code:
HStack (alignment:.center) {
Text("50")
ProgressView("", value: 50, total: 100)
Text("100")
Text("mg")
}
the result is
I want this
I have tried everything like adding alignment center to HStack, adding the progress view inside a VStack, etc
Why Apple never manage to make things related to interface design work intuitively?
There is another way like not using "", and it works as you want.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("50")
ProgressView(value: 50, total: 100) // <<: Here
Text("100")
Text("mg")
}
}
}
The Version that you used is also good, but has another usage like this down code. And it needs the ProgressView pushed down for Text, actually, in your code ProgressView thinks that "" is the information to show! that is why you see that.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("50")
ProgressView("This part is for information to help", value: 50, total: 100)
Text("100")
Text("mg")
}
}
}

Why the selected rows don't stay selected after scrolling in SwiftUI?

Why the selected rows become unselected if the list is scrolled (See the pictures)? Xcode 12.2. iOS 14.2.
I also get a console message:
[Assert] Attempted to call -cellForRowAtIndexPath: on the table view
while it was in the process of updating its visible cells, which is
not allowed.
Update
This seems to be iOS 14.2 bug. I downloaded a simulator for iOS 14.1 version and everything works just fine.
import SwiftUI
struct ContentView: View {
#State private var selectedRows = Set<String>()
var items = ["item1", "item2", "item3", "item4","item5","item6","item7","item8","item9","item10","item11","item12","item13","item14","item15","item16","item17","item18","item19","item20","item21","item22"]
var body: some View {
NavigationView {
List(selection: $selectedRows) {
ForEach(items, id: \.self) { item in
Text(item)
}
}
.listStyle(InsetGroupedListStyle())
.environment(\.editMode, Binding.constant(.active))
}
}
}

SwiftUI: Updating an array item does not update the child UI immediately

I have an array of items (numbers) to be presented to the user using NavigationView, List and a leaf page.
When I update an item (numbers[index] = ...) on a leaf page, it updates the list correctly (which I see when I go back to the list), but not the leaf page itself immediately. I see the change if I go back to the list and re-open the same leaf page.
I would like to understand why it does not update the UI immediately, and how to fix it. Here is the simplified code to re-produce this behavior.
ADDITIONAL INFORMATION
This code works fine on Xcode 12. It fails only on Xcode 12.1 (RC1) and Xcode 12.2 (beta3).
import SwiftUI
struct NumberHolder: Identifiable {
let id = UUID()
let value:Int
}
struct Playground: View {
#State var numbers:[NumberHolder] = [
NumberHolder(value:1),
NumberHolder(value:2),
NumberHolder(value:3),
NumberHolder(value:4),
]
var body: some View {
NavigationView {
List(numbers.indices) { index in
let number = numbers[index]
NavigationLink(destination: VStack {
Text("Number: \(number.value)")
Button("Increment") {
numbers[index] = NumberHolder(value: number.value + 1)
}
} ) {
Text("Number: \(number.value)")
}
}
}
}
}
struct Playground_Previews: PreviewProvider {
static var previews: some View {
Playground()
}
}
Update
Apple has since replied to my Issue stating they have resolved this since watchOS 8 beta 3. I've tested this on WatchOS 9 and iOS 16 and this is indeed now working correctly.
Previous answer:
This had me scratching my day for a few weeks.
It appears there are many features within SwiftUI that do not work in Views that are placed in Lists directly, however if you add a ForEach inside the List said features (such as .listRowPlatterColor(.green) on WatchOS) start to work.
Solution
On iOS 14.2 if you wrap the NavigationLink inside a ForEach the NavigationLink destination (leaf page) will update right away when the data model is updated.
So change your code to
var body: some View {
NavigationView {
List {
ForEach(numbers.indices) { index in
let number = numbers[index]
NavigationLink(destination: VStack {
Text("Number: \(number.value)")
Button("Increment") {
numbers[index] = NumberHolder(value: number.value + 1)
}
} ) {
Text("Number: \(number.value)")
}
}
}
}
}
Frustratingly, this does not solve the issue when using WatchOS, in WatchOS 7.0 the leaf page is updated, however in WatchOS 7.1 (version goes hand in hand with iOS 14.2 that suffered this "issue") so I have an issue open with Apple FB8892330
Further frustratingly, I still don't know if this is a bug or a feature in SwiftUI, none of the documentation state the requirement for ForEach inside of Lists
Try this one. I tested and it works.
import SwiftUI
struct NumberHolder: Identifiable {
let id = UUID()
let value:Int
}
struct ContentView: View {
#State var numbers:[NumberHolder] = [
NumberHolder(value:1),
NumberHolder(value:2),
NumberHolder(value:3),
NumberHolder(value:4),
]
var body: some View {
NavigationView {
List(numbers.indices) { index in
NavigationLink(destination: DetailView(numbers: $numbers, index: index)) {
Text("Number: \(self.numbers[index].value)")
}
}
}
}
}
struct DetailView: View {
#Binding var numbers:[NumberHolder]
let index: Int
var body: some View {
VStack {
Text("Number: \(self.numbers[index].value)")
Button("Increment") {
numbers[index] = NumberHolder(value: numbers[index].value + 1)
}
}
}
}
I found the answer. It was a bug in Xcode.
This code (without any changes) works fine under Xcode 12.0, but fails to update under Xcode 12.2 beta 2.

SwiftUI Form Picker only shows once

I am playing around with SwiftUI, and am currently building a Form using a Picker.
import SwiftUI
struct ContentView: View {
private let names = ["Bill", "Peter", "Johan", "Kevin"]
#State private var favoritePerson = "Bill"
var body: some View {
NavigationView {
Form {
Picker("Favorite person", selection: $favoritePerson) {
ForEach(names, id: \.self) { name in
Text(name)
}
}
}
.navigationBarTitle("Form", displayMode: .inline)
}
}
}
The first time you tap on the "Favorite person" row, the picker shows up fine, and tapping on one of the names brings you back to the form. But tapping on the form row a second time doesn't do anything: you don't go to the picker, the row stays highlighted but nothing happens. If this is a SwiftUI bug, is there a known workaround? (I already needed to use a small navigation bar title to work around the Picker UI bug where otherwise its content moves up when it's shown ☹️)
this issue is just one with the simulator. If you build the app on a physical iOS device, it no longer becomes an issue. It's like that bug with Navigation Link that would only work once.
I have the same problem in Xcode 11.4 and also in the real device.
Picker change didn't call CreateTab, it only worked in initialize.
Picker("Numbers", selection: $selectorIndex) {
ForEach(0 ..< formData.tabs.count) { index in
Text(formData.tabs[index].name).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.onReceive([self.selectorIndex].publisher.first()) { (value) in
print(value)
CreateTab(tabs: formData.tabs, index: self.selectorIndex)
}