Post

Replies

Boosts

Views

Activity

Adding logging to a SwiftUI View's body var
Pretty sure this is a no-no, but asking just in case there's an easy way to make this work struct DocumentContentView: View { private static let logger = Logger( subsystem: "mySubsystem", category: String(describing: Self.self) ) var body: some View { VStack { Text("Hello") logger.trace("hello") } } } This code generates the following compile error at the logger.trace line buildExpression is unavailable: this expression does not conform to View I suspect every line of the body var (or any @ViewBuilder - designated code?) needs to 'return' a View. Is this correct? or more importantly any work arounds other than putting some/all of the view contents in a. func()?
2
0
192
2w
Can't seem to get Unit Test target working when I create. Multiplatform project
In Xcode Version 16.1 Create a new Project, choose Multiplatform, App For testing system, choose XCTest and UI Tests In the project, open the newly generated template Unit test file Click either of the diamonds in the left margin (either to run the example test or the entire file) Expected Result: Code compiles and then Runs the test/tests Actual Result: Code compiles, but then never completes testing (the top status bar is stuck saying "Testing..." forever.) Am I missing something?
0
0
184
Dec ’24
Struggling to add a test target to an existing project in Xcode
Sorry if this question is too vague, however I've tried this multiple times and see the same result. I'm pretty sure I'm doing something wrong, but don't know what. I have a Multiplatform (iOS and macOS) project that builds, and runs I add a new target of type Unit Test Bundle I click the diamond in the margin beside the XCTestCase declaration at the top of the new Test file The target project builds, then Xcode says 'Testing...' and it stays like this forever. I've also tried creating a new scheme that targets the target created in the above steps. attempting to run my tests behaves the same. The top status bar will get stuck saying 'Testing...' and never get anywhere. I'm pretty sure this is something basic. thanks, in advance for any guidance. Mike
2
0
214
Nov ’24
SwiftUI FocusState seems to 'misbehave' when Scene contains a DocumentGroup...
I have a view containing either a TextField or a SecureField. I'm hoping I can use a single FocusState value that will apply to either the TextField or SecureField. (I'm using FocusState to ensure the cursor will be in the field when it initially loads) I've verified this works in a sample project when the view is in a WindowGroup. But when I instead use a DocumentGroup ~50% of the time when the view loads/launches, it does not have focus. Here is my ContentView: struct ContentView: View { let coinFlip: Bool = .random() // used to choose between the TextField and the SecureField @State var fieldContent: String = "" // bound to the Field value @FocusState var focus: Bool var body: some View { VStack { Text("Coin Flip: \(coinFlip)") actualField .focused($focus, equals: true) } .onAppear() { focus = true } } @ViewBuilder var actualField: some View { if coinFlip { TextField("Enter text here", text: $fieldContent) } else { SecureField("Enter secure text here", text: $fieldContent) } } } and here is my App swift file @main struct ModernTurtleApp: App { var body: some Scene { // WindowGroup { // ContentView() // } DocumentGroup(newDocument: ModernTurtleDocument()) { file in ContentView() } } } When this code runs, the Field has focus about 50% of the time on initial launch. When I uncomment the WindowGroup and comment the DocumentGroup, the field always has focus on initial launch. I realize I can work around this behaviour by using an optional enum for FocusState. I would be ok going this route, but I first want to try to understand the behaviour I'm seeing, and possibly keep my simpler FocusState implementation. Thanks, in advance for any help.
0
0
211
Oct ’24
How can I programmatically have focus in a SwiftUI TextField at launch/load
I thought the following code would allow me to have focus in the TextField when the app loads. Is there something else/different that I need to do? struct ContentView: View { enum FocusField { case password } @State var fieldContent: String = "" @FocusState var focus: FocusField? var body: some View { VStack { TextField("Enter text here", text: $fieldContent) .focused($focus, equals: .password) Text("Hello, world!") } .padding() .defaultFocus($focus, .password) } }
1
0
277
Oct ’24
UIDocumentBrowserViewController create new document used to work, but...
is now broken. (but definitely worked when I originally wrote my Document-based app) It's been a few years. DocumentBrowserViewController's delegate implements the following func. func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) { let newDocumentURL: URL? = Bundle.main.url(forResource: "blankFile", withExtension: "trtl2") // Make sure the importHandler is always called, even if the user cancels the creation request. if newDocumentURL != nil { importHandler(newDocumentURL, .copy) } else { importHandler(nil, .none) } } When I tap the + in the DocumentBrowserView, the above delegate func is called (my breakpoint gets hit and I can step through the code) newDocumentURL is getting defined successfully and importHandler(newDocumentURL, .copy) gets called, but returns the following error: Optional(Error Domain=com.apple.DocumentManager Code=2 "No location available to save “blankFile.trtl2”." UserInfo={NSLocalizedDescription=No location available to save “blankFile.trtl2”., NSLocalizedRecoverySuggestion=Enable at least one location to be able to save documents.}) This feels like something new I need to set up in the plist, but so far haven't been able to discover what it is. perhaps I need to update something in info.plist? perhaps one of: CFBundleDocumentTypes UTExportedTypeDeclarations Any guidance appreciated. thanks :-)
1
0
292
Sep ’24
I'm seeing inconsistent results with different types of Binding<>
I've created a UserDefaults extension to generate custom bindings. extension UserDefaults { func boolBinding(for defaultsKey: String) -> Binding<Bool> { return Binding ( get: { return self.bool(forKey: defaultsKey) }, set: { newValue in self.setValue(newValue, forKey: defaultsKey) }) } func cardPileBinding(for defaultsKey: String) -> Binding<CardPile> { return Binding ( get: { let rawValue = self.object(forKey: defaultsKey) as? String ?? "" return CardPile(rawValue: rawValue) ?? .allRandom }, set: { newValue in self.setValue(newValue.rawValue, forKey: defaultsKey) }) } } For the sake of completeness, here is my enum enum CardPile: String, CaseIterable { case allRandom case numbers case numbersRandom case daysMonths case daysMonthsRandom } I've also created UI elements that use these bindings: var body: some View { VStack { Toggle("Enable", isOn: UserDefaults.standard.boolBinding(for: "enable")) Picker("Card Pile", selection: UserDefaults.standard.cardPileBinding(for: "cardPile")) { ForEach(CardPile.allCases, id: \.self) { Text("\($0.rawValue)") .tag($0.rawValue) } } } } When I tap the toggle, it updates correctly. However when I tap the picker and select a different value, the binding setter gets called, but the view does not refreshed to reflect the change in value. (If I force quit the app and re-run it, the I see the change.) I would like to find out why the Binding works as I'd expected (ie updates the UI when the value changes) but the Binding behaves differently. any/all guidance very much appreciated. Note: I get the same behavior when the enum use Int as its rawValue
0
0
410
Jun ’24
How can I subscribe to changes to an @AppStorage var...
From what I've read, @AppStorage vars should be @Published, however the following code generates a syntax error at extended's .sink modifier: Cannot call value of non-function type 'Binding<Subject>' class LanguageManager: ObservableObject { @Published var fred = "Fred" @AppStorage("extended") var extended: Bool = true private var subscriptions = Set<AnyCancellable>() init() { $fred .sink(receiveValue: {value in print("value: \(value)") }) .store(in: &subscriptions) $extended .sink(receiveValue: {value in print("value: \(value)") }) .store(in: &subscriptions) } Does anyone know of a way to listen for (subscribe to) changes in @AppStorage values? didSet works in for a specific subset of value changes, but this is not sufficient for my intended use.
2
0
1k
May ’24
How might I get didSet behaviour on an AppStorage var?
I've defined a value stored in UserDefaults. In a view struct I have code that can successfully update the stored value. I've also added an @AppStorage var in an instance of a class, that can read this value and run business logic that depends on the current stored value. But what I really want to do, is have code in my class that gets automatically called when the value stored in UserDefaults gets updated. Basically I want to do this: @AppStorage("languageChoice") var languageChoice: LanguageChoice = .all { didSet { print("hello") } } Unfortunately didSet closures in @AppStorage vars do not appear to get called :-( My clumsy attempts to use combine have all ended in tears from the compiler. Any/all suggestions are greatly appreciated. thanks, Mike
3
0
775
May ’24
My SwiftUI code is becoming un-SwiftUI-y. I'm looking to make things right again.
I have a singleton instance of a class that (among other things) is managing which subset of words will be available to users. The contents of availableWords will always be a subset of words and is always a function of three userDefaults that are bound to user settings (using @AppStorage) I could dynamically reconstruct availableWords every time it is needed, but it will be read much more frequently than it changes. Because of this, I want to cache the updated list every time a user changes one of the settings that will change its contents. But the only way I can see to do this is to create an update function and rely on the UI code to call the function any time a user updates one of the settings that will require availableWords to be updated. And this feels more like something out of UIKit. Do any of you see a better way of managing the updates of availableWords? class WordsManager { static let shared = WordsManager() let words: Words // defined in init var availableWords: Words // updated anytime scriptPickers, languageChoice or cardPile changes @AppStorage("scriptPickers") var scriptPickers: ScriptPickers = ScriptPickers.defaultDictionary @AppStorage("languageChoice") var languageChoice: LanguageChoice = .all @AppStorage("cardPile") var cardPile: CardPile = .allRandom func updateAvailableWords() { var result = words.filtered(by: cardPile.wordList) let askIdentifiers = languageChoice.askLanguages let answerIdentifiers = languageChoice.answerLanguages result = result.matching(askIdentifiers: askIdentifiers, answerIdentifiers: answerIdentifiers) self.availableWords = result } // other stuff }
3
0
576
May ’24
Does running a single test in a Target containing unit tests start by running the actual app?
In V 0.1 of my app, I went with a simple option for a piece of business logic in my app. I then changed something else that caused my simplified business logic to now crash the app at startup. Excellent, a chance to use Test. Driven Design to replace my flawed business logic. So I wrote my first test TDD test, but when I tried to run the test... It doesn't run because running the test target seems to start by actually running my full app (which as I described in chapter 1 is currently crashing) Have I configured something incorrectly? or is it normal that when I attempt to run a single test, step 1 is running the full app? (note: it is very easy for me to disable/avoid the business logic causing my crash. This question is more about my lack of understanding of what it means to run a test target.) thanks in advance for any and all responses. Mike
1
0
611
May ’24
A syntax error I'm not understanding...
I'm defining a typealias for a set, and then creating an extension for the new typealias. When I do this, I'm getting an odd syntax error. Any/all guidance appreciated. typealias IntSet = Set&lt;Int&gt; extension IntSet { func aFunction() -&gt; Set&lt;String&gt; { let array: [String] = self.map { "\($0)" } return Set(array) } } At the return line, I get the following syntax error: Cannot convert return expression of type 'Set&lt;Int&gt;' to return type 'Set&lt;String&gt;' Even if I replace the return line with the following, I get the same compile error return Set("array")
3
0
598
May ’24
Looking to use @AppStorage, but avoid depending on UserDefaults.standard
I am using @AppStorage in a model object (see code below that works as expected). class ModelObject { static let shared = ModelObject() @AppStorage("enhanced") var scriptPickers: Bool = true var defaultDependentValue: String { scriptPickers ? "Enhanced" : "NOT enhanced" } } struct ContentView: View { @AppStorage("enhanced") var scriptPickers: Bool = true var body: some View { VStack { Toggle(isOn: $scriptPickers, label: { Text("userDefault val") }) Text("value: \(ModelObject.shared.defaultDependentValue)") } } } Now I want to test my model object in a way that will allow me to use a mock instance of UserDefaults, but am having trouble with the syntax. I tried adding a userDefaults var, and referring to the var in the @AppStorage class ModelObject { static let shared = ModelObject() let userDefaults: UserDefaults init(userDefaults: UserDefaults = .standard) { self.userDefaults = userDefaults } @AppStorage("enhanced", store: userDefaults) var scriptPickers: Bool = true var defaultDependentValue: String { scriptPickers ? "Enhanced" : "NOT enhanced" } } However I can't find a way to avoid the syntax error this generates: Cannot use instance member 'userDefaults' within property initializer; property initializers run before 'self' is available Any guidance on how I might be able to: continue using @AppStorage be able to test my class in a way that doesn't force me to use UserDefaults.standard thanks, in advance, Mike
2
0
420
May ’24
Unexpected behaviour when attempting to decode enum keys from a JSON dictionary
This first code works fine decoding json. static let localizationsDictText = """ { "en" : { "string" : "Cat" }, "fr" : { "string" : "Chat"} } """ struct Localization: Codable, Equatable { let string: String } typealias LocalizationsDict = [String: Localization] func testExample() throws { let text = Self.localizationsDictText let data = text.data(using: .utf8, allowLossyConversion: false) let localizationsDict = try JSONDecoder().decode(LocalizationsDict.self, from: data!) XCTAssertEqual(localizationsDict.keys.count, 2) } But then I try to create a modified version of the above, only changing the type of the LocalizationsDict key from String to an enum: enum Language: String, CaseIterable, Codable { case en = "en" case fr = "fr" } typealias LocalizationsDict2 = [Language: Localization] func testExample2() throws { let text = Self.localizationsDictText let data = text.data(using: .utf8, allowLossyConversion: false) let localizationsDict = try JSONDecoder().decode(LocalizationsDict2.self, from: data!) XCTAssertEqual(localizationsDict.keys.count, 2) } and the JSONDecoder line throws an exception: testExample2(): failed: caught error: "typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))" Any suggestions as to why switching from [String: Localization] to [Language: Localization] might cause JSONDecode() to treat it like an array? Thanks in advance for any guidance.
2
0
553
Mar ’24
I want to move a CoreImage task to the background...
It feels like this should be easy, but I'm having conceptual problems about how to do this. Any help would be appreciated. I have a sample app below that works exactly as expected. I'm able to use the Slider and Stepper to generate inputs to a function that uses CoreImage filters to manipulate my input image. This all works as expected, but it's doing some O(n) CI work on the main thread, and I want to move it to a background thread. I'm pretty sure this can be done using combine, here is the pseudo code I imagine would work for me: func doStuff() { // subscribe to options changes // .receive on background thread // .debounce // .map { model.inputImage.combine(options: $0) // return an object on the main thread. // update an @Published property? } Below is the POC code for my project. Any guidance as to where I should use combine to do this would be greatly appreciate. (Also, please let me know if you think combine is not the best way to tackle this. I'd be open to alternative implementations.) struct ContentView: View { @State private var options = CombineOptions.basic @ObservedObject var model = Model() var body: some View { VStack { Image(uiImage: enhancedImage) .resizable() .aspectRatio(contentMode: .fit) Slider(value: $options.scale) Stepper(value: $options.numberOfImages, label: { Text("\(options.numberOfImages)")}) } } private var enhancedImage: UIImage { return model.inputImage.combine(options: options) } } class Model: ObservableObject { let inputImage: UIImage = UIImage.init(named: "IMG_4097")! } struct CombineOptions: Codable, Equatable { static let basic: CombineOptions = .init(scale: 0.3, numberOfImages: 10) var scale: Double var numberOfImages: Int }
1
0
821
Jan ’24