Post

Replies

Boosts

Views

Activity

Reply to barTintColor not working in iOS 15
This doesn't seem to work at all with the iOS 15 RC. VC1 shows VC2. VC1 and VC2 are fine. Their navigation bars are the correct colour. VC2 presents an image picker (of your library photos). Nothing I do seems to make the image picker use the right colours. I've tried to use Rincewind's code to style the picker, but it just shows as light grey, with the nav bar buttons in white. I've implemented navigationController:willShowViewController:animated, but whatever I put in there is completely ignored. It does seem that Apple decide to change the fundamental behaviour of something, and just assume that developers will change their apps to cope with it How about you give us a heads-up before you break our apps?
Topic: UI Frameworks SubTopic: UIKit Tags:
Sep ’21
Reply to "No options were provided for this parameter" in Edit Widget menu
My widgets display this message on the physical device when I try to edit the widget, but not in the Simulator. Environment: Xcode Version 14.0 beta 3 (14A5270f) App written for iOS 14.0 and watchOS 7.0 onwards iOS Simulator on iOS 16.0 or 15.5 Physical iPhone on iOS 15.6 This didn't happen with the app built and deployed via Xcode 13.x to the iPhone. So, is it an Xcode 14.0b3 issue that stops it working on a physical device with iOS 15.6, and is it fixed by having the iPhone on iOS 16? I can't update my iPhone, but if someone out there has had the same issue and has seen it fixed by using Xcode 14 and iOS 16 on a physical device, that would be helpful to know.
Topic: App & System Services SubTopic: Core OS Tags:
Jul ’22
Reply to 'Edit Widget' not working for widget with dynamic options
This is the problem with developing for Apple stuff, it works sometimes, then it doesn't and there is zero help anywhere. My widgets worked fine on iOS 14 on both the iOS Simulator and a physical device. On iOS 15 widgets work fine on the iOS Simulator, and I can select a dynamic value. Run it on a physical device and get "No options were provided for this parameter". Why not? They were fine before, so why are they broken now? Where is the Apple documentation that tells us why this has happened? Nowhere. You're left to figure it out for yourself. Which usually means asking on Star Overflow and waiting until someone else has figured it out, while you spend a week trying to fix something you didn't break. Xcode is pretty poor at giving you hints as to how something can be fixed. Example, accidentally add your IntentHandler.swift file to your main app's target (Target Membership), and you get errors about things not being in scope, but nothing to suggest that the file shouldn't be in that target. Worst of all, what is the experience that my customers are getting? Are their widgets working properly or not?
Topic: App & System Services SubTopic: Core OS Tags:
Jul ’22
Reply to I'm confused about WatchKit and WidgetKit and Watch Apps... Help!
Thanks for your response. A1. Thanks. Makes sense. A2/3. I started work on this version a few weeks ago, and definitely accepted the "update to recommended settings" option, but I do not have a single watch app target. I definitely have the targets I listed in my post. Since starting the new version I've made little improvements here and there, adding new functionality, so I can't go back to an older backup of my code and start from there. Besides, I just opened the previous version with the latest Xcode 14b5, and I don't get "update to recommended settings" in the Issue Navigator. What do I have to do with the "MyApp WatchKit" and "MyApp WatchKit Extension" targets to make them into one app? And, if I do manage to have them combined somehow, does the new target work with both the old Objective-C code and the new SwiftUI stuff I'll be using for the watchOS 9 stuff? A4. Thanks. A5. Sorry, you've confused me there. The utils code is currently in MyApp Widget (the iOS widget target), so I should put it in the "watchOS widget target" - which is that? A2/3 said the update would create a new single watch app target ("MyApp Watch App"?), and you said I don't need to add a new target? A6/7. Are there any examples of how the WKApplicationDelegate looks? Any sample code anywhere? Sometimes I do think Apple need some clearer documentation to show us where stuff should be put. At the moment, looking through WWDC videos to pick up on minutiae is a bit time-consuming.
Topic: App & System Services SubTopic: General Tags:
Aug ’22
Reply to SwiftUI Text Alignment Issue with Date and WidgetKit
I realise this is an old post, but I recently had to do this myself, and I figure this solution is pretty useful (Swift 5, Xcode 14b5). I had an HStack and two Text views within. The second one was a timer: HStack { Text("ABC") Text.init(myDate, style: .timer) .multilineTextAlignment(.center) } No matter what I tried with alignments, padding, frames etc., I couldn't get it work; I got the Text view on the left and the Text.init centred in whatever space remained on the right. But, if you put a Text.init inside a Text view, it seems to work: HStack { Text("ABC... \(Text.init(myDate, style: .timer))") .multilineTextAlignment(.center) } Both the ABC text and the timer are now centred.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to How to refresh a View?
Thanks, but in their Landmarks example the modelData is only ever set once: ModelData.swift:12 = @Published var landmarks: [Landmark] = load("landmarkData.json"). I need to update the data in that model so that it can be used in the content views. Let's say I have a singleton, and the delegate calls the singleton's methods when it receives data. So: The iOS app sends new data to the Watch. The Watch delegate receives the new data. The delegate calls a method in the singleton to store the information in the group defaults. That data is not yet available to the modelData. How do I get that new data into the modelData? final class ModelData : ObservableObject { @Published var items: [ItemDetail] = getItems() } class WatchAppSingleton { @EnvironmentObject var modelData: ModelData ... At this point I can access what's in the modelData, but not write to it. If I try something like modelData.items = <some new items> Xcode tells me: No ObservableObject of type ModelData found. A View.environmentObject(_:) for ModelData may be missing as an ancestor of this. Does the line @Published var items: [ItemDetail] = getItems() execute once or multiple times? How do I get it to grab the data again? I can't be the only person who needs to update the data in their model? Am I just doing it wrong?!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to How to refresh a View?
Ah, right, got it... This: class WatchAppSingleton { @EnvironmentObject var modelData: ModelData should be this: class WatchAppSingleton { @StateObject var modelData: ModelData = ModelData() An error message more like this would be great; modelData is marked as an @EnvironmentObject. Did you mean @StateObject?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to How to refresh a View?
For anyone coming across this thread, here's how I got it to work. It might not be 100% correct, but it's definitely working here. Background: iOS app in Objective-C with a SwiftUI Watch extension ("Watch App"). Watch App has a delegate ("WatchAppDelegate") conforming to WKApplicationDelegate and WCSessionDelegate. WatchConnectivity session delegate methods are in the WatchAppDelegate. WatchAppDelegate also contains my ModelData class. This contains some @Published vars that other Views can see, and will refresh when those values change. The class is also an @ObservableObject. WatchAppDelegate.swift // Instantiate the model data here, *once only*. This is your single source of truth. let modelData: ModelData = ModelData() class ModelData : ObservableObject { @Published var availableItems: [ItemDetail] = defaultsGetAvailableItems() // Gets an array of ItemDetail from the defaults @Published var mainItem: ItemDetail = defaultsGetMainItem() // Again, from the defaults } class WatchAppDelegate : NSObject, WKApplicationDelegate, WCSessionDelegate, ObservableObject { var session: WCSession? override init() { super.init() if(WCSession.isSupported()) { if(session == nil) { session = WCSession.default // Start the Watch Connectivity session session!.delegate = self session!.activate() } } } // Delegate methods go here, session didReceiveMessage etc. ... } When the WCSession delegate methods are called and triggered in WatchAppDelegate, the delegate deals with the new data that's come in. So, for example, the iOS app has just sent a message dictionary with, maybe: "newItems" : arrayOfNewItems. The delegate takes those new items and stores them in the defaults. At that point, you also update the modelData: modelData.availableItems = defaultsGetAvailableItems() and modelData.mainItem = defaultsGetMainItem(). Your one source of truth has been updated with the new data. Okay, the WatchApp is the @main entry point into the app, and you need to start the delegate in there: WatchApp.swift @main struct WatchApp: App { @WKApplicationDelegateAdaptor private var appDelegate: WatchAppDelegate // Launches the delegate, which creates the modelData instance var body: some Scene { WindowGroup { AppContentView(modelData: modelData) .environmentObject(modelData) // Give access to modelData to the receiving view (probably, I dunno, but it's necessary) } } } struct AppContentView : View { @ObservedObject var modelData: ModelData var body: some View { VStack { if(modelData.availableitems.count == 0) { // If there are no items, display an appropriate view and message NoItemsView() .environmentObject(modelData) } else { // We have items ItemsListView() .environmentObject(modelData) } ... } ItemsListView.swift struct ItemsListView: View { @EnvironmentObject var modelData: ModelData // To access the data     var body: some View { VStack { NavigationView { List { // List of items ForEach(modelData.availableItems) { item in NavigationLink { ItemDetailView(item: item) } label: { TableRowView(item: item) } } } } }     } } ItemDetailView.swift struct ItemDetailView: View { @EnvironmentObject var modelData: ModelData var event: ItemDetail // This lets us access the particular item in the array by its index var itemIndex: Int { modelData.availableItems.firstIndex(where: { $0.id == item.id })! } var body: some View { ... Happy to be told what I've done is wrong or bad or bad practice or whatever, but it's working right now...!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to Lock-screen Widget in iOS 16: .accessoryInline without date
I just got stung by this. I've been developing my app in the Simulator for weeks now, and finally needed to update my iPhone to iOS 16. Some of the past few weeks was spent on making the text in the inline widget appear centred. Once it got deployed to the iPhone, the date appears, the image is just a block, and my countdown timer isn't displayed. This means a total rewrite of the inline widget. Would it really hurt Apple to have the Simulator's Lock Screen appear the same as an actual Lock Screen so we know what our widgets are going to look like while we develop them?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to Privacy redaction not working in iOS16 beta widget
If you haven't got this working, try this... Add this extension somewhere: extension View { /// Applies a modifier to a view conditionally. /// /// - Parameters: /// - condition: The condition to determine if the content should be applied. /// - content: The modifier to apply to the view. /// - Returns: The modified view. @ViewBuilder func modifier<T: View>( if condition: @autoclosure () -> Bool, then content: (Self) -> T ) -> some View { if condition() { content(self) } else { self } } /// Applies a modifier to a view conditionally. /// /// - Parameters: /// - condition: The condition to determine the content to be applied. /// - trueContent: The modifier to apply to the view if the condition passes. /// - falseContent: The modifier to apply to the view if the condition fails. /// - Returns: The modified view. @ViewBuilder func modifier<TrueContent: View, FalseContent: View>( if condition: @autoclosure () -> Bool, then trueContent: (Self) -> TrueContent, else falseContent: (Self) -> FalseContent ) -> some View { if condition() { trueContent(self) } else { falseContent(self) } } } Create a little helper function somewhere that saves you a bunch of typing. Pass in your redactionReasons variable: func getHideInfo(_ redactionReasons: RedactionReasons) -> Bool { return (redactionReasons.contains(.privacy) || redactionReasons.contains(.placeholder)) } In your view body, add this line: let hideInfo: Bool = getHideInfo(redactionReasons) i.e.: var body: some View { let hideInfo: Bool = getHideInfo(redactionReasons) ... } Then, on your views, Text(), Image() etc. add this; .modifier(if: hideInfo) { $0.redacted(reason: .placeholder) } else: { $0.unredacted() } i.e.: Text("Hello World!") .modifier(if: hideInfo) { $0.redacted(reason: .placeholder) } else: { $0.unredacted() } What that does is redact the view if it's meant to be private, and unredact it if not. It's pretty easy having that one modifier on a view. Remember to use the different controls on the preview canvas in Xcode to see the "Presentation" variants. It will show you what your widget looks like normally, privacy sensitive, placeholder and luminance reduced. Hope this helps.
Topic: App & System Services SubTopic: General Tags:
Aug ’22