Post

Replies

Boosts

Views

Activity

How to Bind Data Between Parent Model and Child Model Classes in SwiftUI?
I have a Model class containing the main data (@Published var counter: Int), which is automatically updated periodically by a Timer. I am considering displaying the Model.counter value in a SheetView. I believe a simple case would look like this. @main struct BindingSampleApp: App { var body: some Scene { WindowGroup { ContentView() } } } @MainActor class ContentModel: ObservableObject { @Published var counter: Int = 0 @Published var isShowingSheet = false init() { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in Task { @MainActor in self.counter += 1 print("counter: \(self.counter)") } } } func showSheet() { isShowingSheet = true } } struct ContentView: View { @StateObject var model = ContentModel() var body: some View { VStack { Button() { model.showSheet() } label: { Text("Show. \(model.counter)") } } .sheet(isPresented: $model.isShowingSheet) { SheetView(counter: model.counter) } .padding() } } struct SheetView: View { let counter: Int var body: some View { VStack { Text("\(counter)") .font(.largeTitle) } } } Due to the increasing complexity of SheetView's logic, I've created a separate SheetModel. What I want to achieve is to display the value of Model.counter, which is updated by a timer, in SheetView by relaying it through SheetModel. After some trial and error, I have come up with the following two pieces of code that seem to achieve the desired behavior. However, I am not sure if this is the appropriate way to use SwiftUI. Specifically, for the first code snippet, self.sheetModel = SheetModel(counter: self._counter), I suspect it may not be ideal because it probably causes both Model and SheetModel to reference the same object. For the second snippet, SheetModel.init(counter: Published.Publisher) { counter.assign(to: &self.$counter) }, it feels a bit verbose. Is there a simpler and better way to bind Model.counter and SheetModel.counter? (Tested on Xcode Version 14.3.1 (14E300c), iOS simulator 16.4) First @main struct BindingSampleApp: App { var body: some Scene { WindowGroup { ContentView() } } } @MainActor class ContentModel: ObservableObject { @Published var counter: Int = 0 @Published var sheetModel: SheetModel? var isShowingSheet: Bool { get { sheetModel != nil } set { if !newValue { sheetModel = nil } } } init() { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in Task { @MainActor in self.counter += 1 print("counter: \(self.counter)") } } } func showSheet() { // I suspect it may not be ideal because it probably causes both Model and // SheetModel to reference the same object. self.sheetModel = SheetModel(counter: self._counter) } } @MainActor class SheetModel: ObservableObject { @Published var counter: Int = 0 init(counter: Published<Int>) { print("SheetModel.init") self._counter = counter } } struct ContentView: View { @StateObject var model = ContentModel() var body: some View { VStack { Button() { model.showSheet() } label: { Text("Show. \(model.counter)") } } .sheet(isPresented: $model.isShowingSheet) { SheetView(model: model.sheetModel!) } .padding() } } struct SheetView: View { @ObservedObject var model: SheetModel var body: some View { VStack { Text("\(model.counter)") .font(.largeTitle) } } } Second @MainActor class ContentModel: ObservableObject { @Published var counter: Int = 0 @Published var sheetModel: SheetModel? var isShowingSheet: Bool { get { sheetModel != nil } set { if !newValue { sheetModel = nil } } } init() { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in Task { @MainActor in self.counter += 1 print("counter: \(self.counter)") } } } func showSheet() { self.sheetModel = SheetModel(counter: $counter) } } @MainActor class SheetModel: ObservableObject { @Published var counter: Int = 0 init(counter: Published<Int>.Publisher) { print("SheetModel.init") // it feels a bit verbose. counter.assign(to: &self.$counter) } } // ContentView and SheetView are same as above.
0
0
600
Sep ’23
Product.SubscriptionInfo.status(for:) is sometimes empty even though a subscription has been registered
My app calls Product.SubscriptionInfo.status(for:) to get the subscription status when the app starts. Users with multiple Apple IDs have reported that every few days they get an unpurchased status, and when I checked that, the Product.SubscriptionInfo.status(for:) result array was empty. (When the app is restarted, Product.SubscriptionInfo.status(for:) gives the correct result.) StoreKit.Transaction.currentEntitlements, which is executed immediately after Product.SubscriptionInfo.status(for:), seems to be getting the correct result, so I am trying to check the subscription status with this result. Is it a bug that Product.SubscriptionInfo.status(for:) returns an empty result for the purchaser? There is a mismatch between Product.SubscriptionInfo.status(for:) and StoreKit.Transaction.currentEntitlements. Is it possible for a mismatch to occur?  And In such a case, which result should be adopted?
1
0
959
Oct ’24
iOS 17β SwiftUI: Differences in BottomBar Button Layout Between iOS 16 and Earlier, and iOS 17
The layout of buttons placed in the bottomBar of the iOS 17β SwiftUI toolbar is different from that in iOS 16 and earlier versions. When I run the following code in Xcode 15 β3 (15A5195k) and the iOS 17β Simulator, all the buttons on the BottomBar are aligned to the left. In environments including iOS 17β and earlier versions of iOS 16 with Xcode 14.3.1 (14E300c), only the last button is aligned to the right, while the other buttons are aligned to the left. Is this a specification change in iOS 17, or is it a bug in iOS 17β? import SwiftUI @main struct ToolBarSampleiOS17BApp: App { var body: some Scene { WindowGroup { NavigationView { ContentView() } .navigationViewStyle(.stack) } } } struct ContentView: View { var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("iOS 17") } .toolbar { ToolbarItemGroup(placement:.bottomBar) { Button("A") { } Button("B") { } Button("C") { } Button("D") { } Button("E") { } } } .padding() } } Xcode 15 β3 (15A5195k) and the iOS 17β Simulator earlier versions of iOS 16
1
0
1.1k
Jul ’23
How to Retrieve the Previous Price of an AutoRenewable Subscription Product in StoreKit2?
Hi, Recently, I have changed the price of an auto-renewable subscription. At that time, I made sure that existing subscribers maintain the previous price. The issue is that product.displayPrice in StoreKit2 returns the new price even for those users who were able to maintain the old price. Is there a way to retrieve the previous (actually applied to the user) price using StoreKit2? Thank you.
1
0
770
Dec ’23
When using keyboardShortcut, instance of StateObject is not deinitialized
When adding a keyboardShortcut to a Button that references the @StateObject model in Button.action as shown below, model.deinit is not called even if this view has disappeared. On redisplaying, both model.deinit and model.init are called. Without the keyboardShortcut, model.deinit is called when the screen has disappeared. Also, when I explicitly specified [weak model = self.model], the deinit was called. Is it a bug that we need to use weak when using keyboardShortcut? Test environment Xcode Version 15.0 (15A240d) iOS 17.0 Simulator iOS 16.4 Simulator, (iPhone SE 3rd generation) import SwiftUI @main struct StateObjectDeinitSampleApp: App { var body: some Scene { WindowGroup { NavigationStack { MainView() } } } } struct MainView: View { var body: some View { NavigationLink(value: true) { Text("GO") } .navigationDestination(for: Bool.self) { value in if value { ContentView() } } } } struct ContentView: View { @StateObject var model = Model() var body: some View { VStack { Button("Toggle") { model.flag.toggle() } .keyboardShortcut(.space, modifiers: []) } .padding() } } @MainActor class Model: ObservableObject{ @Published var flag: Bool = false init() { print("Model.init") } deinit { print("Model.deinit") } } // weak. struct ContentView: View { @StateObject var model = Model() var body: some View { VStack { Button("Toggle") { [weak model = self.model] in model?.flag.toggle() } .keyboardShortcut(.space, modifiers: []) } .padding() } }
1
0
592
Oct ’23
startAccessingSecurityScopedResource always returns false for files in iCloud Drive folder
Hello, I am trying to use the SwiftUI fileImporter to get the URL of a direcotry and access it for the files in it. If I follow the document( Access the directory’s content ) and use url.startAccessingSecurityScopedResource for each file, it always returns false. but this seems to be different from the documentation. If the current behavior is correct, will the documentation be updated in the future? Related: Access all files in UIDocumentPick… | Apple Developer Forums I asked this question because I am concerned that if I remove the standardAccessingSecurityScopedResource to match the current situation, the standardAccessingSecurityScopedResource may become necessary due to future iOS updates. Also, is there any way to know if the status of the AccessingSecurityScopedResource? It would be helpful if we could callstartAcesingSecurityScopedResource only when needed. Thanks Here is a sample code @main struct SampleApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @State private var isShowingImportFolder = false @State private var isShowingImportFile = false var body: some View { VStack(spacing: 48) { Button("Read Folder") { isShowingImportFolder = true } .fileImporter(isPresented: $isShowingImportFolder, allowedContentTypes: [.folder]) { result in switch result { case .success(let url): processDirectory(url) case .failure(let error): print("failed. error: \(error)") } } Button("Read File") { isShowingImportFile = true } .fileImporter(isPresented: $isShowingImportFile, allowedContentTypes: [.data]) { result in switch result { case .success(let url): readFile(url) case .failure(let error): print("failed. error: \(error)") } } } } private func processDirectory(_ directory: URL) { guard directory.startAccessingSecurityScopedResource() else { fatalError("failed. directory.startAccessingSecurityScopedResource") } defer { directory.stopAccessingSecurityScopedResource() } var error: NSError? NSFileCoordinator().coordinate(readingItemAt: directory, error: &error) { url in let urls = try! FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.nameKey, .isDirectoryKey]) urls.lazy .filter { !$0.hasDirectoryPath } .forEach { readFile($0) } } } private func readFile(_ url: URL) { guard url.startAccessingSecurityScopedResource() else { print("failed access. \(url.path)") return } defer { url.stopAccessingSecurityScopedResource() } let data = try! Data(contentsOf: url) print("file.path: \(url.path()), size: \(data.count)") // read file... } }
1
0
1.6k
Mar ’24
App Only Crashes on Launch in iOS 17.4 for Production Version
Hello, I'm encountering an issue where my released app fails to launch only on iOS 17.4. The version of the app released through TestFlight works fine without any issues. Specifically, when the app installed from the App Store is launched on iOS 17.4, it immediately crashes. However, I've noticed the following: If I turn off the network connection, such as putting the device in Airplane Mode, the app launches successfully. Once the app is launched, I can re-enable the network connection, and the app continues to run without crashing. My app uses StoreKit2 for handling transactions and connections with the App Store. It initiates a connection to the App Store via StoreKit2 at launch. The primary difference between the TestFlight version and the production version is the App Store endpoint they connect to. This leads me to suspect that there might be an issue with the connection to the App Store. (Another possibility is that the app communicates with Firebase or Google Admob, so there could be an issue with these SDKs as well.) This issue only occurs in the production version, making it difficult to investigate. Are there any suggestions on what I can do to further diagnose this issue? You can download my app from here: https://apps.apple.com/us/app/repeatable-player-cut-loop/id616310281 I can provide the TestFlight URL if needed. Any help or guidance would be greatly appreciated.
1
1
3.4k
Mar ’24
Is ObservableObject implicitly a MainActor in iOS 16 and later?
This is a question regarding the specification of ObservalObject. The following code does not cause a compilation error when the deployment target is set to iOS 16, but it does cause the following error when set to iOS 15: class Model: ObservableObject { let player: AVPlayer @Published var isPlaying = false var playerObserver: NSKeyValueObservation? init() { self.player = AVPlayer() self.playerObserver = self.player.observe(\.rate, options: [.new, .old]) { player, change in NSLog("changed rate: \(String(describing: change.newValue))") } } func setup() { let name = "sample" let url = Bundle.main.url(forResource: name, withExtension: "m4a")! let playerItem = AVPlayerItem(url: url) self.player.replaceCurrentItem(with: playerItem) } func play() { self.player.play() self.isPlaying = true } } The following error occurs in iOS 15: Cannot form key path to main actor-isolated property 'rate' Call to main actor-isolated instance method 'play()' in a synchronous nonisolated context Additionally, if the code is modified as follows, the error does not occur even in iOS 15. Specifically, adding @MainActor to the Model resolves the issue. @MainActor class Model: ObservableObject { ... } From this behavior, I guessed that ObservableObject is implicitly a MainActor in iOS 16 and later. Is this understanding correct?
1
0
618
Jul ’24
StateObject is not deinitialized when List(selection:) binding
Hello, I have a simple example using StateObject and List. When I bind the List(selection:) to a property of the StateObject like this: List(selection: $viewModel.selectedIndex) { ... } I noticed that each time I push the view using a NavigationLink, a new instance of the StateObject is created. However, when I pop the view, the deinit of the StateObject is not called. When is deinit actually expected to be called in this case? Example code: import SwiftUI @main struct NavigationViewDeinitSampleApp: App { var body: some Scene { WindowGroup { NavigationStack { ContentView() } } } } struct Item: Hashable { let text: String } @MainActor fileprivate class ContentViewModel: ObservableObject { @Published var selectedIndex: Int? = nil init() { NSLog("ContentViewModel.init") } deinit { NSLog("ContentViewModel.deinit") } } struct ContentView: View { @StateObject private var model = ContentViewModel() let items: [Item] = { return (0...10).map { i in Item(text: "\(i)") } }() var body: some View { List(selection: $model.selectedIndex) { ForEach(items.indices, id: \.self) { idx in let item = items[idx] NavigationLink { ContentView() } label: { Text(item.text) } } } } } Interestingly, if I instead use a plain @State variable inside the View: @State private var selectedIndex: Int? ... List(selection: $selectedIndex) { ... } Then the deinit of the StateObject does get called when the view is popped. Because there's no sign of deinit being triggered in the first pattern, I’m starting to suspect this might be a SwiftUI bug. Has anyone seen this behavior or have more information about it? Thanks in advance. Environment: Xcode: 16.4(16F6) iOS Simulator: iPhone SE3 iOS16.4(20E247),iPhone SE3 iOS 18.4(22E238)
1
0
137
Jul ’25
List Layout Breaks in NavigationStack When a View Exceeds Screen Width
This is a bug report. FB17433985 The layout of the following ContentView appears correctly when it is outside a NavigationStack. However, when the same view is placed inside a NavigationStack, the layout breaks. It seems that the width of the List is being affected by the width of the buttonsView, which exceeds the screen width. In my testing, this issue occurs on iOS 18.4 and later, but does not occur on iOS 18.2 or iOS 17.5. Workaround I found: Remove the fixed width modifier from the Button If anyone knows of other ways to resolve this issue without affecting the layout, I would appreciate it if you could share them. import SwiftUI let values = (1...100).map { $0 } let buttonTitles = (1...9).map { "Button\($0)" } struct ContentView: View { var body: some View { VStack { List { Section { ForEach(values.indices, id: \.self) { val in HStack { Text("Index: \(val)") } } } } buttonsView } } private var buttonsView: some View { HStack { ForEach(0..<buttonTitles.count, id: \.self) { index in Button() { } label: { Image(systemName: "square.and.arrow.up") .resizable() .frame(width: 48, height: 48) } } } } } @main struct ButtonShapeBugApp: App { var body: some Scene { WindowGroup { if true { NavigationStack { ContentView() } } else { ContentView() } } } } Environment: Xcode Version 16.3 (16E140) iPhone 18.4.1 real device iPhone SE3rd 18.4 simulator Expect layout Broken layout(9 buttons) Broken layout(10 buttons)
2
0
79
May ’25
navigationDestination(isPresent:content) in NavigationStack doesn't work on iOS16.1 beta
Hi, I've just finished replacing to NavigationStack, but I'm having trouble with this issue. The following simple sample code works on iOS 16.0 production, but it doesn't work on iOS 16.1 beta. When navigationDestination(isPresent:) is called, Xcode displays runtime warning message, but I cannot understand what the means. A `navigationDestination(isPresented:content:)` is outside an explicit NavigationStack, but inside the detail column of a NavigationSplitView, so it attempts to target the next column. There is no next column after the detail column. Did you mean to put the navigationDestination inside a NavigationStack? I didn't use NavigationSplitView and navigationDestination(isPresent:) is inside NavigationStack. Could someone please point out the problem with this code? Or is it a SwiftUI bug? Test environment Xcode Version 14.1 beta 2 (14B5024i) iPhone SE (3rd gen) (iOS 16.1) Sample Code @main struct StackViewSampleApp: App {     var body: some Scene {         WindowGroup {             NavigationStack {                 ContentView()             }         }     } } struct ContentView: View {     var body: some View {         List {             NavigationLink("SubView1") {                 SubView1()             }             NavigationLink("SubView2") {                 SubView2()             }         }     } } struct SubView1: View {        @State private var isPresent: Bool = false      var body: some View {         Button("A") {             isPresent = true         }         .navigationDestination(isPresented: $isPresent) {             Text("TEST")         }     } } struct SubView2: View {     enum ViewType {         case a, b, c     }     @State private var selection: ViewType? = nil     let viewTypes: [ViewType] = [.a, .b, .c]     var body: some View {         List(selection: $selection) {             ForEach(viewTypes, id: \.self) { t in                 NavigationLink(value: t) {                     Text("\(String(describing: t))")                 }             }         }         .navigationDestination(isPresented: .init(get: {             selection != nil         }, set: { newValue in             if !newValue {                 selection = nil             }         })) {             Text("\(String(describing: selection))")         }     } }
3
0
2k
Dec ’22
NavigationLink in NavigationStack doesn't work on iOS 16.0 and iOS 16.1 beta
The following sample code doesn't work as expected. In addition, the behavior differs between iOS 16.0 and iOS 16.1 beta. If there is a workaround, please let me know. (I know that navigationDestination(isPresent:destination:) can be substituted in iOS 16.0, but it doesn't work in iOS 16.1 beta) I reported this issue (FB11599516). @main struct StackViewSampleApp: App {     var body: some Scene {         WindowGroup {             NavigationStack {                 ContentView()             }         }     } } struct ContentView: View {     var body: some View {         VStack {             NavigationLink("SubView") {                 SubView()             }         }         .navigationDestination(for: Int.self) { v in             DetailView(value: v)         }     } } struct SubView: View {     var body: some View {         NavigationLink(value: 1) {             Text("DetailView")         }         .navigationTitle("SubView")     } } struct DetailView: View {     let value: Int     var body: some View {         Text("\(value)")             .navigationTitle("Detail")     } } Actual behavior iOS 16.0 behavior / Xcode Version 14.0 (14A309) Pressing "SubView" button in RootView, it goes to SubView. Pressing "DetailView" button in SubView, it goes to DetailView. Pressing "Back" button, it goes to RootView. (Expect: it goes to SubView) iOS 16.1 beta(20B5045d) / Xcode Version 14.1 beta 2 (14B5024i) Pressing "SubView" button in RootView, it goes to SubView. Pressing "DetailView" button in SubView, it goes to SubView. Pressing "DetailView" button in SubView, it goes to SubView. (Repeat) (Expect: it goes to DetailView) Pressing "Back" button in SubView, it goes to DetailView. (Expect: it goes to a previous view)
3
0
1.7k
Jan ’23
Infinite Loop Issue with View's onAppear and onDisappear in iOS 17 Beta
While testing my application on iOS 17 beta, I encountered an issue where the onAppear and onDisappear functions of the View inside the List went into an infinite loop, rendering the application inoperable. This did not occur with iOS 16 or iOS 15. Is this a bug with the iOS 17 beta? Or did the specifications change with iOS 17? Here is the reproduction code. On iOS 17 beta, as soon as the application starts, the onAppear and onDisappear of SampleSection go into an infinite loop. Testing Environment: Xcode: Version 14.3.1 (14E300c), Version 15.0 beta 3 (15A5195k) Device: iPhone SE (2nd) iOS 15.2, iPhone SE (3rd) iOS 16.4, iPhone SE(3rd) iOS17 beta 3 import SwiftUI @main struct SampleApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { var body: some View { List { SampleSection() section // This issue occurs even without this line. } } private var section: some View { Section { ForEach(1...100, id: \.self) { idx in Text("\(idx)") } } } } struct SampleSection: View { @State private var isLoaded = false var body: some View { let _ = Self._printChanges() Group { if !isLoaded { Section("Header") {} .hidden() } else { Section("Header") { Text("Text") } } } .onAppear { NSLog("SampleSection onAppear.") isLoaded = true } .onDisappear() { NSLog("Sample Section onDisappear.") isLoaded = false } } }
3
0
1.7k
Jul ’23
.highPriorityGesture Prevents Button Tap on iOS 17 and Earlier
In iOS 18, using .highPriorityGesture does not interfere with Button tap detection. However, on iOS 17 and earlier, setting a .highPriorityGesture causes the Button's tap action to stop responding. I am using .highPriorityGesture in an attempt to support both tap and long-press gestures on a Button, which works as expected in iOS 18. However, the same implementation prevents taps on earlier versions. Is using .highPriorityGesture on a Button not recommended in this case? Or is this an issue specific to how .highPriorityGesture behaves on iOS 17 and earlier? Below is a sample code: struct SampleButton: View { let title: String let action: () -> Void var body: some View { Button { NSLog("Tapped") action() } label: { Text(title) }.highPriorityGesture(LongPressGesture(minimumDuration: 0.5).onEnded { _ in NSLog("Long press.") action() }) } } struct ContentView: View { var body: some View { VStack { SampleButton(title: "Tap or LongPress") {} } } } Environment: Xcode: Version 16.3 (16E140) iOS: iOS 18.4(simulator), iOS 17.5, iOS 16.4, iOS 15.2
3
0
107
May ’25
FocusState not working as expected in iOS 17(Public Beta)
I've been testing my app on iOS 17 Public Beta and noticed a bug where the FocusState always returns nil depending on the combination of Views. This issue did not occur on iOS 16 and earlier. Below is the code where FocusState does not work. As far as I've checked, the issue only occurs in the case below. Does anyone have a workaround? Test environment: Xcode Version 15.0 (15A240d) iOS 17.0(21A329) (iPhone) @main struct SampleApp: App { var body: some Scene { WindowGroup { ContentView() ///<-- 1 } } } struct ContentView: View { @State var isShowingCover = false var body: some View { Button("Show") { isShowingCover = true } .fullScreenCover(isPresented: $isShowingCover) { NavigationStack { ///<-- 2 SheetView() } } } } struct SheetView: View { @State private var texts: [String] = [ "file", "folder", ] @FocusState var focusing: Int? var body: some View { VStack { Text("focusing: \(String(describing: focusing))") List { ///<-- 3 TextFields(texts: $texts, focusing: $focusing) } Button(">") { // FocusState always becomes nil in iOS 17 if let focusing { self.focusing = (focusing + 1) % texts.count } else { self.focusing = 0 } } } } } public struct TextFields: View { @Binding var texts: [String] var focusing: FocusState<Int?>.Binding public var body: some View { HStack { ForEach(texts.indices, id: \.self) { idx in TextField("", text: $texts[idx]) .focused(focusing, equals: idx) .underline(idx == focusing.wrappedValue) } } } } Interestingly, removing the NavigationStack within fullScreenCover makes FocusState work as expected. (2) Also, if the ContentView in WindowGroup is changed to NavigationStack { SheetView() } (1) or the List (3) is removed, FocusState still works as expected. /// 1 @main struct MultilineFieldSampleApp: App { var body: some Scene { WindowGroup { // ContentView() NavigationStack { SheetView() // FocusState works. } } } } ///2 struct ContentView: View { @State var isShowingCover = false var body: some View { Button("Show") { isShowingCover = true } .fullScreenCover(isPresented: $isShowingCover) { // NavigationStack { SheetView() // Also, FocusState works. // } } } } /// 3 struct SheetView: View { // ... var body: some View { VStack { Text("focusing: \(String(describing: focusing))") // List { TextFields(texts: $texts, focusing: $focusing) // FocusState works. // } Button(">") { if let focusing { self.focusing = (focusing + 1) % texts.count } else { self.focusing = 0 } } } } }
5
2
2k
Oct ’23