Post

Replies

Boosts

Views

Activity

Reply to Stumped by URLSession behaviour I don't understand...
Thank you @DTS Engineer and @DTS Engineer (aka Quinn and Kevin) I started this task primarily to get a handle on using async/await. I came feeling smarter in quite a few areas, including background processing, URLSession tradeoffs and fileProtection write options. I'm feeling a bit sheepish, as in hindsight I probably should have tried removing all the options in my data.write(to: localUrl... call. oops. Thanks, both of you for the time and effort to help me work through my issue and teaching me so much along the way. Oh, I almost forgot, Kevin, your suggestion to remove the fileProtection write option, solved the problem I describe in my initial post.
1w
Reply to Stumped by URLSession behaviour I don't understand...
Thank you @DTS Engineer for the energy you put into helping us write better code, I really appreciate your commitment to this. I had a couple of mostly unrelated points/responses I want to give a bit of background to describe my original goals/intentions (just in case there is a better option I have not considered) Something interesting my 'research' uncovered Background I've created an app for fetching and listening to podcasts. It includes a scan function that: scans subscribed podcasts to see if there are known episodes it should download makes api calls to fetch podcast metadata to see if there are any 'new' episodes fetch these new episodes if we aren't currently storing too much new content (eg just because we have meta data for 10 episodes that are 2 hours each, don't download them all right away) The code that does this work has multiple URLSession callbacks, state information and multiple error cases that need to be handled. The flow is a bit complex. I thought this functionality would be better implemented using a single code path with async/await. At this point, based on what I've learned here it feels like async/await won't meet my needs because I want them to continue if the app moves to the background. (if a scan starts while the app is in the foreground, I want the user to be able to move the app to the background and have the scan continue; and also continue playing audio; as you pointed out, two complete independent flavours of background task) So at this point, absent a third option I'm not yet aware of, I'm going to stick with my more complex completionHandler-based implementation of this scan function. Something. I discovered Out of curiosity, I modified my option1 implementation to use URLSession.shared instead of background-capable instance and..... option1 still works. I think this means my audio dropouts are not related to whether the URLSession is background able or not. If I had to guess, the only difference I see between the two options is the steps to put the data blob in a file. In option1, URLSession creates the file in a temporary location and my code merely moves that file to the required location. In option2, my code actually creates the file. My current assumption is that there is something in the way my code is writing the file that is causing the problem. But at this point, even if I can uncover the cause of the audio drop outs, I'll still be stuck as I don't see a way to use the data task, as they only work while the app is in the foreground.
1w
Reply to Stumped by URLSession behaviour I don't understand...
@DTS Engineer I think you're on to something... however I feel like your question is uncovering another wrinkle. The two code paths in my original question use different URLSessions. The version that is able to play the files (option1) uses: let config = URLSessionConfiguration.background(withIdentifier: "MySession") config.isDiscretionary = false config.sessionSendsLaunchEvents = true return URLSession(configuration: config, delegate: nil, delegateQueue: nil) The version that is unable to play once the app is backgrounded (option2) uses: URLSession.shared NOTE: In both cases the download has completed before I begin playback. In both cases I've been able to confirm I'm receiving identical complete blobs of data before playback begins. Having said that ^, I still wanted to see what happens if I update option2 to use the background URLSession used in option1. When I update the URLSession code of option2 to match option1, the app throws: Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.' I understand that session.data(for: request) is a convenience func that uses a completionHandler under the hood. And I conceptually see how this approach may be problematic with async/await. Might it be possible to use async/await to do inline downloading that can continue in the background? (I realize this might be a greedy thing to be asking for :-) ) (This question is somewhat prefaced on the assumption that somehow the URLSessionConfiguration may be having an effect on the playback behaviour of otherwise identical blobs of audio data. I say somewhat because I would very much prefer my async/await implementation to also support background downloading irrespective of the playback issue)
2w
Reply to Calendar's date func is not behaving as I'd expect...
Thanks @Claude31 for pointing me to a couple of helpful sources. I definitely understand that changing weekday will have unpredictable results for day, month, year. but I'm still not sure I understand why hour, minute need to get clobbered. Having said that, thanks to your links I've changed my approach and am using Calendar's nextDate func mutating func update(updatedWeekday: Int? = nil, updatedHour: Int? = nil, updatedMinute: Int? = nil) { var components = DateComponents() components.weekday = updatedWeekday == nil ? weekday : updatedWeekday components.hour = updatedHour == nil ? hour : updatedHour components.minute = updatedMinute == nil ? minute : updatedMinute let nextDate = Self.calendar.nextDate(after: underlyingDate, matching: components, matchingPolicy: .nextTime, direction: .forward) if let nextDate = nextDate { underlyingDate = nextDate } }
Topic: App & System Services SubTopic: General Tags:
Oct ’25
Reply to Is it possible to make GeometryReader less vertically greedy?
I realized, in hindsight my original question was likely too specific. Stepping back, my high level requirements are: Display an image show a draggable overlay 'marker' view update a stateVar/modelProperty unitVector CGSize value with the marker's location (eg top left would be 0,0, centre would be 0.5, 0.5, bottom right would be 1,1) 4. marker should not be able to be dragged beyond the edges of the image I now feel like my initial plan to do GeometryReader { ZStack { Image marker } } Might not be the right approach. I feel like the 'hard part' will be determining the marker's position relative to the Image. Any guidance on this is greatly appreciated.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jan ’25
Reply to Struggling to add a test target to an existing project in Xcode
Hi Szymczyk, thanks for your questions :-) What version of Xcode are you running? 16.1 Can you run the unit tests if you create a new project and select the Include Tests checkbox? No. I see the same 'bad' behaviour when I do the following: Create a new project and choose: Multiproject and Document App (and include Unit and UI test targets) choose iPhone 16 simulator as the target, Build and Run (app builds and runs fine) In Xcode, open the Tests file auto created in the Unit tests target click the diamond to the left of the final class declaration Build succeeds, but then it spins forever at Testing... I repeated the above steps, but instead chose App instead of Document App, and I see the same results. I repeated the above steps again, but chose iOS (instead of Multiplatform) and App. In this case Xcode runs (and passes) the tests. I ran one final test using iOS and Document App. In this case Xcode successfully runs the tests. I'll look further, but currently it seems like Multiplatform projects in Xcode aren't able to run test targets for me. thanks again for the questions that helped me move this forward a bit :-) Mike
Nov ’24
Reply to How can I subscribe to changes to an @AppStorage var...
I found an implementation I'm mostly happy with. First, I defined a Notification.Name and created a userDefaults binding 'factory' extension Notification.Name { static let userDefaultsChanged = Notification.Name(rawValue: "user.defaults.changed") } struct BindingFactory { static func binding(for defaultsKey: String) -> Binding<Bool> { return Binding { return UserDefaults.standard.bool(forKey: defaultsKey) } set: { newValue in UserDefaults.standard.setValue(newValue, forKey: defaultsKey) NotificationCenter.default.post(name: .userDefaultsChanged, object: defaultsKey) } } } Then in my UI elements that were previously using @AppStorage, they now use the new bindings. var body: some View { VStack { Toggle("Extended", isOn: BindingFactory.binding(for: "extended")) Text(LanguageManager.shared.summary) } } And now LanguageManager's init can add a subscriber to the userDefaultsChanged notification. init() { NotificationCenter.default.publisher(for: .userDefaultsChanged) .sink(receiveValue: { notification in print("\(notification.object!) changed") self.updateItems() }) .store(in: &subscriptions) } The main thing I don't really like about this is the need to create a static func binding(for defaultsKey: String) -> Binding<Bool> for each type of binding (Bool, String, Int, etc.)
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’24
Reply to How might I get didSet behaviour on an AppStorage var?
Hi again @Claude31 turns out I was curious enough about this to make time to investigate it sooner. In my sample code, neither the View's willSet/didSet nor the class's willSet/didSet are getting called. import SwiftUI struct ContentView: View { @AppStorage("extended") var extended: Bool = true { willSet { print("ConteentView willSet") } didSet { print("ContentView didSet") } } var body: some View { VStack { Toggle("Extended", isOn: $extended) Text(LanguageManager.shared.summary) } .padding() } } #Preview { ContentView() } class LanguageManager { static let shared = LanguageManager() var items: [String] = ["basic", "list"] var summary: String { return items.reduce("") { return "\($0)\n\($1)" } } @AppStorage("extended") var extended: Bool = true { willSet { print("LanguageManager willSet") } didSet { print("LanguageManager didSet") updateItems() } } func updateItems() { if extended { items = ["much", "more", "than", "basic", "list"] } else { items = ["basic", "list"] } } } ** Update**: I now see I should have followed the link you included. Sorry about not doing that before answering. However this answer suggests that when a user updates the UI, the @AppStorage var in my class will most definitely not be getting updated. And that is the one that I'm hoping will be able to be notified when 'somebody else' updates one of the UserDefault values. I want to define multiple UserDefault values that can be updated via settings/prefs UI. and then when any of these values change, LanguageManager will be notified (pub/sub?) of the change, and run updateItems()
Topic: UI Frameworks SubTopic: SwiftUI Tags:
May ’24
Reply to Looking to use @AppStorage, but avoid depending on UserDefaults.standard
Good suggestion @Claude31. I just tried adding this: struct UserDefaultsKey: EnvironmentKey { static var defaultValue: UserDefaults = .standard } extension EnvironmentValues { var userDefaults: UserDefaults { get { self[UserDefaultsKey.self] } set { self[UserDefaultsKey.self] = newValue } } } and updating the original to include this: @Environment(\.userDefaults) var userDefaults @AppStorage("enhanced", store: userDefaults) var scriptPickers: Bool = true I now get the following error one the @AppStorage line: Cannot use instance member 'userDefaults' within property initializer; property initializers run before 'self' is available bummer...
Topic: UI Frameworks SubTopic: SwiftUI Tags:
May ’24
Reply to I want to move a CoreImage task to the background...
I think I've found something that works. Moved CombineOptions into the model, and subscribe to the options changes in the Model's init. Still, I'd appreciate any feedback on this code below. struct ContentView: View { @ObservedObject var model = Model() var body: some View { VStack { Image(uiImage: model.composed) .resizable() .aspectRatio(contentMode: .fit) Slider(value: $model.options.scale) Stepper(value: $model.options.numberOfImages, label: { Text("\(model.options.numberOfImages)")}) } .padding() } private var enhancedImage: UIImage { return model.inputImage.combine(options: model.options) } } class Model: ObservableObject { let inputImage: UIImage = UIImage.init(named: "IMG_4097")! @Published var options = CombineOptions.basic @Published var composed: UIImage private var cancellables: [AnyCancellable] = [] init() { self.composed = inputImage $options .debounce(for: 1.0, scheduler: DispatchQueue.global(qos: .background)) .map( { mapOptions in return UIImage.composed(from: self.inputImage, using: mapOptions) } ) .receive(on: DispatchQueue.main) .assign(to: \.composed, on: self) .store(in: &cancellables) } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jan ’24
Reply to How to delete container in CloudKit Dashboard?
Heavy Sigh on this missing functionality. When I tried moving my dev schema to production, it failed and didn't give a reason. Attempting to debug, I created a second container just by adding a 2 at the end of the identifier. the new container works fine, so I tried deleting all the Record Types from the original. I then created a new recordType with a single custom field. it still fails with no reason given. Then I thought I'd just delete and recreate the original container...... apparently not :-(
May ’21