Post

Replies

Boosts

Views

Activity

What thread does Combine `.collect` method emit on?
What is the expected thread on which a .collect operation in Swift's Combine will emit? Specifically I am seeing this code crash in the second precondition, but not the first: return Publishers.MergeMany(publishers) .handleEvents(receiveOutput: { _ in precondition(Thread.isMainThread) // <- This is fine }) .collect() .handleEvents(receiveOutput: { _ in precondition(Thread.isMainThread) // <- Crash is here }) It is as if the .collect operation is picking a different thread to use, even though the final publisher in the MergeMany must have emitted on the main thread. I have deduced this behavior via crash logs uploaded from Firebase. Can anyone explain this observed behavior?
0
1
642
May ’23
SwiftUI onAppear called inconsistently based on the presence of listStyle
I have discovered that the onAppear method of views inside of a SwiftUI list is called inconsistently, based on the presence of a listStyle. The onAppear method is called 100% of the time when there is no listStyle applied, but it is called irregularly when there is a listStyle applied. Here is demo code: struct TextRow: View {   @State private var didAppear: Bool = false   private let title: String   init(title: String) {     self.title = title   }   var rowTitle: String {     if didAppear {       return title + " (didAppear)"     } else {       return title     }   }   var body: some View {     Text(rowTitle)       .onAppear {         didAppear = true       }   } } struct Section: Hashable {   let title: String   let rows: [String] } struct ContentView: View {   var content: [Section] {     var rows = [String]()     for i in 0..<20 {       rows.append("Row \(i)")     }     let section1 = Section(title: "Section 1", rows: rows)     var rows2 = [String]()     for i in 0..<20 {       rows2.append("Row \(i)")     }     let section2 = Section(title: "Section 2", rows: rows2)     return [section1, section2]   }   var body: some View {     List {       ForEach(content, id: \.self) { section in         Text(section.title)         ForEach(section.rows, id: \.self) { row in           TextRow(title: row)         }       }     } //    .listStyle(.grouped) // <-- this will trigger different behavior     .navigationBarTitle("Countries")     .padding()   } } Is this expected? Here is the bad behavior: Here is the proper behavior (no list style):
6
1
4.3k
Sep ’22
SwiftUI LazyVStacks - when and how do they use Identifiable?
I'm trying to debug an issue where, after the update of a @Published value on an @ObservedObject in a LazyVStack, the cells don't appear to redraw. Imagine I have this app below, that draws 18 cells, each representing a different country on a continent (apologize for the ugly colors): I have a an array of Country objects that I render as cells onscreen. Each Country conforms to Identifiable, and uses a monotonically increasing number to implement the Identifiable protocol. My question is this: imagine this @ObservedObject, or, in this demo code below, @State, changes somehow. What is returned is a list of identical size, where every Model object returns a monotonically increasing ID. How does the LazyVStack know that it should redraw? I wrote the code below and I feel that it should not work, yet somehow it does. When I click on the "Asia" button, or I click on the "Africa" button, it renders the way I expect and immediately updates the cells -- yet I feel that clicking on the opposite button should have no effects, because it will be loading a collection of the same size, with the same IDs, and so SwiftUI should effectively re-use the old cells. That scenario I am expecting is the bug that I believe, but cannot yet prove, my actual app is experiencing. I am attempting to write demo code to prove this theory. Here is the complete demo code that I believe should reproduce this issue (but somehow is working fine). import SwiftUI public struct Continent {   public let name: String   public let countries: [Country]   public init(name: String, countries: [Country]) {     self.name = name     self.countries = countries   } } extension Continent: Hashable {   public func hash(into hasher: inout Hasher) {     hasher.combine(countries)   } } public struct Country: Identifiable, Equatable {   public var id: Int {     return index   }   public let regionCode: String   public let index: Int   public init(regionCode: String, index: Int = 0) {     self.regionCode = regionCode     self.index = index   }   public var countryName: String? {     Locale.current.localizedString(forRegionCode: regionCode)   } } extension Country: Hashable {   public func hash(into hasher: inout Hasher) {     hasher.combine(index)   } } struct ContentView: View {   @State var continents: [Continent] = continentListAfrica   @ViewBuilder private func Grid(stack: Continent, columns: Int) -> some View {     let chunks = stride(from: 0, to: stack.countries.count, by: columns).map {       Array(stack.countries[$0..<min($0 + columns, stack.countries.count)])     }     Text(stack.name)     ForEach(chunks, id: \.self) { chunk in       GridRow(chunk: chunk, stack: stack, columns: columns)     }   }   @ViewBuilder private func GridRow(chunk: [Country], stack: Continent, columns: Int) -> some View {     let emptyElements = columns - chunk.count     HStack(spacing: 5) {       ForEach(chunk) { country in         Cell(title: country.countryName ?? "Unknown")       }       if emptyElements > 0 {         ForEach(0..<emptyElements, id: \.self) { _ in           Cell(title: "Blank")         }       }     }   }   @ViewBuilder private func CountryListView(continentList: [Continent]) -> some View {     LazyVStack(spacing: 0) {       ForEach(continentList, id: \.self) { stack in         Grid(stack: stack, columns: 3)       }     }   }   var body: some View {     VStack {       ScrollView {         CountryListView(continentList: continents)       }       HStack {         Button {           self.continents = ContentView.continentListAsia         } label: {           Text("Asia")         }         Button {           self.continents = ContentView.continentListAfrica         } label: {           Text("Africa")         }       }     }     .padding()   }   private static let continentListAfrica: [Continent] = [     Continent(name: "Africa", countries: [       Country(regionCode: "dz", index: 1),       Country(regionCode: "bi", index: 2),       Country(regionCode: "cm", index: 3),       Country(regionCode: "td", index: 4),       Country(regionCode: "km", index: 5),       Country(regionCode: "eg", index: 6),       Country(regionCode: "er", index: 7),       Country(regionCode: "sz", index: 8),       Country(regionCode: "et", index: 9),       Country(regionCode: "gm", index: 10),       Country(regionCode: "gh", index: 11),       Country(regionCode: "gn", index: 12),       Country(regionCode: "ke", index: 13),       Country(regionCode: "lr", index: 14),       Country(regionCode: "ly", index: 15),       Country(regionCode: "mw", index: 16),       Country(regionCode: "mr", index: 17),       Country(regionCode: "mu", index: 18)     ])   ]   private static let continentListAsia: [Continent] = [     Continent(name: "Asia", countries: [       Country(regionCode: "af", index: 1),       Country(regionCode: "bd", index: 2),       Country(regionCode: "bt", index: 3),       Country(regionCode: "bn", index: 4),       Country(regionCode: "ps", index: 5),       Country(regionCode: "id", index: 6),       Country(regionCode: "ir", index: 7),       Country(regionCode: "iq", index: 8),       Country(regionCode: "kw", index: 9),       Country(regionCode: "lb", index: 10),       Country(regionCode: "my", index: 11),       Country(regionCode: "mv", index: 12),       Country(regionCode: "mm", index: 13),       Country(regionCode: "om", index: 14),       Country(regionCode: "pk", index: 15),       Country(regionCode: "qa", index: 16),       Country(regionCode: "sa", index: 17),       Country(regionCode: "sg", index: 18)     ])   ] } struct Cell: View {   private let title: String   public init(title: String) {     self.title = title   }   public var body: some View {     ZStack {       Text(title.prefix(10))         .background(Color.purple)     }.frame(width: 125, height: 125)       .background(Color.green)   } } struct ContentView_Previews: PreviewProvider {   static var previews: some View {     ContentView()   } }
0
0
1.1k
Nov ’22
SwiftUI List scroll indicator stutters, does not call `onAppear` or `onDisappear` consistently in iOS 16
My team has been debugging problems with the SwiftUI List component this week. We have found that it's performance is sub-optimal on iOS 16. You can see a simple grid of images, the scroll indicator stutters when scrolling down: Now compare it to what happens when we use a ScrollView with a LazyVStack: (An error occurred while uploading my second image, but pretend you see a scroll indicator moving smoothly down the side of the screen). You can see the scroll indicator moves smoothly without issue. We also found that the ScrollView combined with a LazyVStack properly calls onDisappear, which enables us to call a cancel method on the async image loading code that we use for our individual cells in this example. Though in a previous question, it was asserted that onDisappear cannot be reliably expected to be called in a List, I do not feel that answer is correct or proper behavior. Is this a bug, or is this expected behavior on a List? This is the cell that is being rendered: struct UserGridCell: View {   let stackId: String   let user: ProfileGridCellUIModel   let userGridCellType: UserGridCellType   @State var labelFrame: CGRect = .zero       private var isOnlineAcessibilityValue: String {     return user.isOnline == true ? "" : ""   }       init(stackId: String,      user: ProfileGridCellUIModel,      userGridCellType: UserGridCellType   ) {           self.stackId = stackId     self.user = user     self.userGridCellType = userGridCellType   }   var body: some View {     GeometryReader { containerGeometry in       ZStack(alignment: .bottom) {         HStack(spacing: 4) {                       let statusAccentColor: Color = .red                                   Circle()             .frame(width: 8, height: 8)             .foregroundColor(statusAccentColor)                       Text(String(user.remoteId) ?? "")             .lineLimit(1)             .foregroundColor(.black)             .overlay(GeometryReader { textGeometry in               Text("").onAppear {                 self.labelFrame = textGeometry.frame(in: .global)               }             })         }         .frame(maxWidth: .infinity, alignment: .bottomLeading)         .padding(.leading, 8)         .padding(.trailing, 8)         .padding(.bottom, 8)       }       .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)       .contentShape(Rectangle())       .accessibilityLabel(Text(user.name ?? ""))       .accessibilityValue(isOnlineAcessibilityValue)     }     .background(       ZStack {         AsyncProfileImage(request: URLRequest(url: URL(string: "https://picsum.photos/id/\(100 + user.remoteId)/200/300")!))       }         .accessibilityHidden(true)     )     .overlay(       RoundedRectangle(cornerRadius: 4)         .stroke(.red, lineWidth: user.hasAnyUnreadMessages ? 4 : 0)     )     .cornerRadius(4)   } } This is the code that renders each cell: struct ProfileGrid: View {   public static var AspectRatio: CGFloat = 0.75   @Environment(\.horizontalSizeClass) var horizontalSizeClass   @Environment(\.redactionReasons) private var reasons   private let stacks: [ProfileGridStackUIModel]   public init(stacks: [ProfileGridStackUIModel]   ) {     self.stacks = stacks   }       var body: some View {     let columnCount: Int = 3           // If you use a list, you will get the stutter. If you use what you see below, // it will render properly.     ScrollView {       LazyVStack {         ForEach(stacks, id: \.self) { stack in           Grid(stack: stack, columns: columnCount)         }       }     }     .buttonStyle(PlainButtonStyle())     .listStyle(PlainListStyle())   }       @ViewBuilder private func Grid(stack: ProfileGridStackUIModel, columns: Int) -> some View {     let chunks = stride(from: 0, to: stack.profiles.count, by: columns).map {       Array(stack.profiles[$0..<min($0 + columns, stack.profiles.count)])     }                   ForEach(chunks, id: \.self) { chunk in       GridRow(chunk: chunk, stack: stack, columns: columns)         .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))     }   }       @ViewBuilder private func GridRow(chunk: [ProfileGridCellUIModel], stack: ProfileGridStackUIModel, columns: Int) -> some View {     let emptyElements = columns - chunk.count     HStack(spacing: 8) {       ForEach(chunk) { user in         UserGridCell(stackId: "id",                user: user,                userGridCellType: .grid)         .aspectRatio(ProfileGrid.AspectRatio, contentMode: .fill)       }               if emptyElements > 0 {         ForEach(0..<emptyElements, id: \.self) { _ in           Rectangle()             .foregroundColor(Color.clear)             .contentShape(Rectangle())             .frame(maxWidth: .infinity)             .aspectRatio(ProfileGrid.AspectRatio, contentMode: .fill)         }       }     }   } }
1
0
1.8k
Oct ’22
iOS user with language of "en", no region code, translation lookup fails
We have a bug reported where a user has a device with an en language and nil region code, and thus all NSLocalizedString lookups in are failing, meaning our string key is what is rendered onscreen. Thus, if we had this in our en.lproj/Localizable.strings file: "some_key" = "Some string."; It would render some_key instead of Some string. in our UI. First question: how do I replicate this scenario locally? This question on Stack seems to almost describe the issue, but does not describe how one enters this state. Second question: why would iOS not fall back to English in the event the region code was nil?
3
0
1.4k
Sep ’22
Crash in SVGFilterPrimitive::draw on iOS 16
We have suddenly started seeing this crashing stack on iOS 16. Does anyone have any workarounds? 0 libsystem_platform.dylib _platform_memmove + 420 arrow_right 1 vImage vImageCopyBuffer + 824 2 libCGInterfaces.dylib _vImageCreateCGImageFromBuffer + 2368 3 CoreSVG CIImage* SVGFilterPrimitive::inputImage<CIImage>(SVGAtom::Name) const + 88 4 CoreSVG SVGFilterPrimitive::drawFeComposite() const + 72 5 CoreSVG SVGFilterPrimitive::selectPrimitive() const + 280 6 CoreSVG SVGFilterPrimitive::draw(FilterContext&, FilterResult**, std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, FilterResult*, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, FilterResult*> > >&, CGSize, CGSize) + 72
0
0
848
Aug ’22
UIScreen isCaptured always true
Can anyone explain why an iPhone might always be returning true for the UIScreen.isCaptured property, even after the device has been fully restarted and there is no screen mirroring or recording taking place? Are there any non-obvious scenarios where it would always be true? We have a user reporting an issue that can only be explained by this property incorrectly or erroneously coming back as true.
1
0
744
Oct ’21
Extreme keyboard lag in iOS 14
I have some customers reporting extreme keyboard lag when typing anything in our app. I have two movies demonstrating this behavior: As well as this movie: I really don't believe that this is related to any of our code -- this is the standard keyboard in both cases, and if somehow our code was blocking the UI thread the OS would kill our app. We are unable to reproduce this behavior in-house and this only seems to apply to a handful of our customers. If you search "keyboard lag" on Apple dev forums you will see numerous threads: https://developer.apple.com/forums/thread/5071 https://developer.apple.com/forums/thread/79442?answerId=235108022#235108022 https://developer.apple.com/forums/thread/665191?answerId=656127022#656127022 https://developer.apple.com/forums/thread/671132 However, most of these threads are very old and it's not clear that there is or was a resolution. This issue seems to have persisted across multiple releases of iOS 14 for our members. We opened a ticket with Apple DTS but they were unable to offer any guidance. Has anyone here experienced this issue themselves, and can anyone offer a workaround beyond hoping that this is an iOS issue that Apple will fix in the future?
0
0
981
Jun ’21
Should files uploaded via uploadTask go in temp or caches directory?
When using uploadTask(with:fromFile:) - https://developer.apple.com/documentation/foundation/urlsession/1411550-uploadtask to perform an upload in the background, should the fileUrl be kept in the temp directory: NSTemporaryDirectory() Or should it instead be the Caches directory? NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) I ask because I am starting to see this crashing stack: Fatal Exception: NSInvalidArgumentException Cannot read file at file:///private/var/mobile/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-CB39D5E080D9/tmp/org.alamofire.manager/multipart.form.data/XXXXXXXX-XXXX-XXXX-XXXX-28C54136FC0A Coming from: Request.swift - Line 583partial apply for closure #2 in UploadRequest.Uploadable.task(session:adapter:queue:) + 583 And I suspect that the file is maybe being deleted while also being actively used by a background uploadTask? The Apple File System basics docs - https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW2 say this about data in the temporary directory: [you] cannot rely on these files persisting after your app terminates It says this about caches: Cache data can be used for any data that needs to persist longer than temporary data, but not as long as a support file NOTE: though this crashing stack is coming from Alamofire, - https://github.com/Alamofire/Alamofire I am in the process of re-writing it from scratch using pure-native Apple APIs, so assume for the purposes of this question that Alamofire is not part of the stack. Where should I be writing temporary files for upload?
3
0
1.3k
Feb ’21