Post

Replies

Boosts

Views

Activity

Can you use different descriptive text for adding Home Screen & Lock Screen widgets?
My app lets you create a list of items and pick one as the main item. For Home Screen widgets there are two bits of text you can use in the panel that appears when you want to add the widget: public var body: some WidgetConfiguration { IntentConfiguration(kind: "myWidgetKind", intent: DynamicSelectionIntent.self, provider: Provider()) { entry in MyWidgetEntryView(entry: entry) } .configurationDisplayName("Title") .description("Description") So, for a Home Screen widget the panel displays the title and description, and the preview shows the main item. Once you've added the widget you can edit it and pick a different item, so the title is "Display an Item" and the description is quite general, telling you what the widget can display. The title and description are also displayed in the panel when you want to add a Lock Screen widget: For the inline Lock Screen widget, you see "Title". For rectangular and circular widgets below the clock, you see the usual Home Screen panel, so both "Title" and "Description". There's no way of editing the item that sits behind the Lock Screen widget once you've added it, so the general text needs to be more specific and refer to the main item. How do you give different title and description text when you're adding a Lock Screen widget?
1
0
1.1k
Aug ’22
SF Symbol as Image in .accessoryInline Lock Screen widget not rendered
In the first image you can see my app at the top of the list, and Apple's Weather app widget. Both are showing the SF Symbol rendered correctly. In the second image the inline widget chosen is Apple's Weather app. The SF Symbol is correctly shown. In the third image my app's inline widget has been chosen, but the image is not rendered correctly; it's just a block. Is there something I have to do to my image to have it rendered correctly?
1
0
962
Aug ’22
watchOS 9 complications - where do they go?
I have an app with Home Screen widgets, and new Lock Screen widgets, and I'm trying to get my complications to work on the Watch. The widgets are running off dynamic intents, taking a list of items from user defaults and providing that list for the user to choose when they add a Home Screen or Lock Screen widget. This works fine. In the "Complications and widgets: Reloaded" video from WWDC2022 at about 7:00 the guy tells you to duplicate the widget extension, rename it, and set it to run on watchOS. I ended up with these targets: Main App (embeds Widget, Intents Handler and Watch App) Widget Intents Handler Watch App (embeds Complications) Complications (new, copy of Widget) At about 8:30 he adds the supported families for the Watch to his EmojiRangersWidget WidgetConfiguration, so it looks like the code for the accessory widgets is used for both Lock Screen widgets and complications? Should I be putting all my complications views in the Widget target, something like this? @main struct WidgetEntry: Widget { public var body: some WidgetConfiguration { IntentConfiguration(kind: kWidgetKind, intent: DynamicItemSelectionIntent.self, provider: Provider() ) { entry in WidgetEntryView(entry: entry) } .configurationDisplayName("abc") .description("def") #if os(watchOS) .supportedFamilies([.accessoryCircular, .accessoryInline, .accessoryRectangular, .accessoryCorner]) #else .supportedFamilies([.accessoryCircular, .accessoryInline, .accessoryRectangular, .systemSmall, .systemMedium]) #endif } } struct WidgetEntryView: View { var entry: Provider.Entry @Environment(\.widgetFamily) var family @ViewBuilder var body: some View { #if os(watchOS) switch family { case .accessoryCircular, .accessoryCorner, .accessoryInline, .accessoryRectangular: ComplicationView(item: entry.item) @unknown default: UnknownComplicationView(item: entry.item) } #else switch family { case .accessoryCircular, .accessoryInline, .accessoryRectangular: LockScreenWidgetView(item: entry.item) case .systemSmall: SmallWidgetView(item: entry.item) case .systemMedium, .systemLarge, .systemExtraLarge: MediumWidgetView(item: entry.item) @unknown default: UnknownWidgetView(item: entry.item) } #endif } } I've previously been told by an Apple engineer on these forums: "The complication code should be in the watch app target. That's where watchOS 7 and 8 will look for complications, and where watchOS 9 will look for old ClockKit complications for migration to their WidgetKit counterparts." I'm no longer supporting watchOS 7/8, but does this mean that watchOS 7/8 will look for old complications in there to migrate to the new stuff into the Widget target? I asked a couple of follow-up questions but they never got answered ¯\_(ツ)_/¯ Apple make this stuff so unnecessarily complex, even though their videos make it look so easy. How many times have we all paused their videos to see exactly what code they're writing and where they're putting it? There's practically zero help out there - these forums are full of questions and few answers. Xcode should have much better documentation and help to guide you through this. It takes so long to get anything done because there just isn't the information we need.
1
0
2.1k
Sep ’22
So many spam posts recently
... all talking about customer care numbers. Maybe Apple should make these Dev Forums just for signed-in Developers? If a Developer spams, they can be blocked. But if just anyone can register and post, they can spam, get banned, then start a new account. Or, Apple should implement some way of blocking the spam before it gets posted. I really don't understand what these spammers think they're getting out of doing this? Who is going to look through the forums and think, "Oh, a customer care number for something I've never heard of? I should phone that immediately!" Really does prove that spammers are dumb.
1
2
977
Sep ’22
Partial fix for widgets & complications not showing correctly
I recently raised this post explaining how I couldn't seem to get watchOS 9 complications to work, and I've figured out a partial fix. The original post details the issues with complications - and some are still valid - but this fix applies to both my complications and Home Screen / Lock Screen widgets. I was following the various WWDC 2020/2022 videos and the Emoji Rangers sample code, adding bits here and there, and assuming they were completely valid. Sadly, this bit of code in the widget's dynamic intents IntentTimelineProvider getTimeline really just banjaxed everything: // Create entries for one day, 15 minutes apart let currentDate = Date() for minuteOffset in stride(from: 0, to: 60 * 60 * 24, by: 15) { let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: currentDate)! entries.append(EventEntry(date: entryDate, event: event)) } If I remove that, and generate a different timeline with specific dates and times (for example: now, in 10 mins, in 2 hours, in a day, etc.) the complications appear correctly, as do Home Screen and Lock Screen widgets. The outstanding issues with complications are: The previews all use the same data, but getSnapshot() is supposed to return the data specific to that event from the configuration, i.e. if let theId = configuration.event?.identifier. "Christmas" is correct, but "Gallery Opening" is using Christmas's data. Once I've selected the event I want to use in a complication the edit screen shows it as totally blank, not even a placeholder: I hope this little fix works for you guys. And, if you know how to fix the above issues, let me know. (iOS 16.1 beta 1, Xcode 14.1 beta 1)
1
1
2.6k
Apr ’24
How to handle this situation with data that changes while it's on-screen
I have a settings screen in my ObjC iOS app and in the SwiftUI Watch app. When you change something in the Watch app's screen and click the Save button it sends a dictionary of the new values to the iOS app. The iOS app receives it and updates its settings in the defaults, and also updates the Settings screen in the iOS app if it's on-screen when you made the changes on the Watch. I post a notification to update the Settings screen, and this works fine. I now need to handle it the other way round, i.e. you make changes in the iOS app, it sends a dictionary to the Watch, the Watch receives it and applies the settings, then updates the Watch app's Settings screen if it was open when the settings changed. I added the setting values in the Watch app to my model data, and the settings do correctly update when they're changed by the iOS app. However, the controls within the Settings screen on the Watch don't update because they're tied to @State vars, not the modelData vars. Here's some pseudocode to explain that: var newMode: Int = modelData.mode // Get the initial value from the modelData struct SettingsView: View { @State private var mode = newMode var body: some View { Text("\(modelData.mode)") // This updates when settings are changed in the iOS app Text("\(mode)") // This doesn't update Text("\(newMode)") // This doesn't update Picker("Mode", selection: $mode) { Text("0")).tag(0) Text("1")).tag(1) Text("2")).tag(2) } .pickerStyle(.navigationLink) .onChange(of: mode) { value in updateMode(value) } ... } func updateMode(_ value: Int) { newMode = value } In the two Text() lines that don't update it's understandable because they aren't tied to anything external to the screen, but even if I add an extra .onChange like this it doesn't update the value in the UI: .onChange(of: modelData.mode) { value in mode = value newMode = mode } The idea of the Settings screen is to allow the user to make changes, and either save their changes and apply them, or cancel and revert to the original settings. I don't want to update the settings unless the user specifically presses the "Save" button, and I really don't want to remove the "Save" button and simply apply the changes each time a change is made as I'd be sending a dictionary of data to the iOS app each time - I have a stepper in there, which would send it every time the value changed, which could be tens of times. So, the problem is that although the modelData.mode value is correctly changed by the receipt of new data from the iOS app, the Settings controls on the Watch do not update because they aren't tied to it. How do I get that to work? Is there a way of updating the newMode value and where it's displayed in the UI when the modelData changes?
1
0
799
Oct ’22
Access iOS app CoreData in Swift extension?
This should be simple, but it's proving immensely difficult and annoying. I have three targets in the project: an iOS app written in Objective-C a Watch App written in Swift/SwiftUI a Widget Extension with my widgets and complications in, written in Swift/SwiftUI I want to access the Core Data that's got all my app's data in it from the Widget Extension and Watch App. How do I do this? Every internet search result assumes all your targets are in Swift, and it's really not feasible for me to rewrite the entire app. All three targets have the same App Group set up, and I can access UserDefaults in each target. I have a CoreData.swift class in a shared folder, and it's a member of both the Watch App and Widget Extension targets. It has this in it: lazy var persistentContainer: NSPersistentContainer = { let storeURL = URL.storeURL(for: kAppGroup, databaseName: kCoreDataModel) let storeDescription = NSPersistentStoreDescription(url: storeURL) let container = NSPersistentContainer(name: kCoreDataModel) container.persistentStoreDescriptions = [storeDescription] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("CoreData: Failed to initialise Managed Object Model from url: \(storeURL), with error: \(error), \(error.userInfo)") } }) return container }() and the storeURL extension is: public extension URL { static func storeURL(for appGroup: String, databaseName: String) -> URL { guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { fatalError("CoreData: Shared file container could not be created.") } return fileContainer.appendingPathComponent("\(kCoreDataModel).sqlite") } } To be clear, the main iOS app (in Objective-C) has created the Core Data model etc., and it all works fine. I just want to be able to access the data in that model from the other targets. (I only need read access.) When I deploy to a device the stack looks like this: NSURL *dataModelFolderURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kAppGroup] URLByAppendingPathComponent:kCoreDataFolder]; Is: /private/var/mobile/Containers/Shared/AppGroup/9F329A90-C897-4AA2-87DF-D98A9E85356A/data-model NSURL *modelURL = [[NSBundle mainBundle] URLForResource:kCoreDataModel withExtension:@"momd"]; Is: file:///private/var/containers/Bundle/Application/CA9E3697-C4C6-48CB-89EA-CC441A6F81AF/MyApp.app/TheDataModel.momd/ // Wait until we have the store, or fetches will sometimes return no data [self performSelectorOnMainThread:@selector(getStore) withObject:nil waitUntilDone:YES]; storeURL = file:///private/var/mobile/Containers/Shared/AppGroup/9F329A90-C897-4AA2-87DF-D98A9E85356A/data-model/TheDataModel I've tried getting the Swift targets to look in a bunch of different paths, but nothing works. When I try to get a count of objects in the Core Data I always get zero results. The second of those outputs is from the bundle. I guess that's because the data model is held within the iOS app's structure? Does that make any difference? Should it be held somewhere else? If so, what should it be? (Minor rant: There really should be an option in Xcode that says, "This core data is shared here, here and here, and all you need to do is type CoreData.getSomeData() and you're done", but no, we have to write boilerplate code for everything.) Anyway... any help is appreciated.
1
0
1.1k
Oct ’23
App structure for iOS app, widgets, watchOS app & complications?
I've had to rewrite my app to get widgets working properly, and I've got this project structure: Main iOS app: Bundle identifier = com.me.myapp Contains: Widget Extension: Bundle identifier = com.me.myapp.widgets Targets iOS 17 Provides widgets to iOS Watch app: Bundle identifier = com.me.myapp.watchapp Contains: Complications Extension (a Widget Extension): Bundle identifier = com.me.myapp.watchapp.complications Targets watchOS 10 Provides widgets to watchOS On the Signing & Capabilities tab in Xcode 15, all four targets have: Provisioning Profile: Xcode Managed Profile Signing Certificate: Apple Development: My team App Groups: all use the same one: group.com.me.myapp I can build and deploy to a physical iPhone and Apple Watch, but the Watch app doesn't seem to be able to access the shared Core Data, which should be in the shared app group container. When running the main iOS app, the store location is: file:///private/var/mobile/Containers/Shared/AppGroup/189E5907-E6E4-4790-833F-06944E4FF5FF/data-model/TheDataModel When running the widget extension, the store location is: file:///private/var/mobile/Containers/Shared/AppGroup/189E5907-E6E4-4790-833F-06944E4FF5FF/data-model/TheDataModel When I run the Watch app, the store location is different: file:///private/var/mobile/Containers/Shared/AppGroup/55381E6D-410E-4322-93BA-64BD1933909E/data-model/TheDataModel How do I get the Watch app to see the Core Data store from the main app? Do I have to replicate the store to that location every time something changes in the main app? Or do I have to go back to sending massive data sets as dictionaries/data via WatchConnectivity?
1
0
1.8k
Oct ’23
Lightweight migration in Swift/UI with CoreData
Hey all. I've been rewriting my Objective-C app in SwiftUI (it's going well, thanks!) and I'm hitting this issue that I thought I'd solved ages ago. I have version 5.1 of my original ObjC app using version 11 of the Core Data model. I've created a new version 12, added a couple of new attributes, and created a mapping model from 11 to 12. The fields I've added are booleans, and I've set them to have a default value of NO so that new entries will automatically get NO, but there are existing records that don't have that field at all, and I'd like them to also get NO (or YES, depending on something in the other fields, but I haven't figured out how to do that yet). This is how Core Data is set up in the new Swift target: public extension URL { // Returns a URL for the given app group and database pointing to the sqlite database static func storeURL(for appGroup: String, databaseName: String) -> URL { guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)?.appending(path: kCoreDataFolder) else { logger.error("Shared file container could not be created.") fatalError("CoreData: Shared file container could not be created.") } return fileContainer.appendingPathComponent("\(kCoreDataModel)") } } // MARK: - Persistence Controller struct CoreData { static let shared = CoreData() let container: NSPersistentContainer init() { let storeURL = URL.storeURL(for: kAppGroup, databaseName: kCoreDataModel) let storeDescription = NSPersistentStoreDescription(url: storeURL) container = NSPersistentContainer(name: kCoreDataModel) container.persistentStoreDescriptions = [storeDescription] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { logger.error("Failed to initialise Managed Object Model from url: \(storeURL), with error: \(error), \(error.userInfo)") fatalError("CoreData: Failed to initialise Managed Object Model from url: \(storeURL), with error: \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true } When I run the old app on the Simulator it adds some demo data (using model v11), and the app functions properly. If I then install the new version - using v12 - it fails with a Core Data error because it couldn't migrate the data. (Apologies, I cannot get the exact error at the moment as I'm part-way through redoing some other stuff and the app won't build.) I've read somewhere that "When we use the NSPersistentContainer class to create and manage the Core Data stack, we don't need to do any additional setup work, the lightweight migration is automatically activated for us." But if that's the case, why does my app crash? I'll try and get the exact error for you... How do I implement lightweight migration in Swift? This page suggests creating a persistent coordinator and adding a couple of options, but I can't quite figure out how to do that with the code I already have, and each time I try it seems to beat the original store so I keep going back to the above code because it works.
1
0
1.3k
Mar ’24
Complying with the EU's Digital Services Act: Trader status
According to this Apple page, if you make any money from your apps in the EU you have to provide your email address, phone number and address, and they will be displayed on your App Store page for all and sundry to see, use, and likely, abuse. I don't want anyone and everyone to know those details; they are private. I thought Apple was all about privacy? I understand they have to adhere to the DSA, but Apple hasn't raised a single objection to this. Apple has consistently said that not sharing a user's email address with a developer is a part of being in the App Store, i.e. Spotify can't contact someone who downloaded their app; but a user can now contact the developer? I barely make any money from my apps - not even enough to cover the annual developer program fee - but I keep developing to stay current. I cannot afford a PO Box or business address and phone number to shield me from this, so I'm likely to remove my apps from the EU market. You might think I'm being overly-cautious, or having a knee-jerk reaction, but these are my personal, private details, and they should not be available publicly just because I barely clear £1.50 a month from my apps.
1
1
774
Aug ’24
How to draw emojis like the Lock Screen customisation?
On iOS you can create a new Lock Screen that contains a bunch of emoji, and they'll get put on the screen in a repeating pattern, like this: When you have two or more emoji they're placed in alternating patterns, like this: How do I write something like that? I need to handle up to three emoji, and I need the canvas as large as the device's screen, and it needs to be saved as an image. Thanks! (I've already written an emojiToImage() extension on String, but it just plonks one massive emoji in the middle of an image, so I'm doing something wrong there.)
1
0
602
Dec ’24
Can't use Link in .systemLarge widget
I just added a .systemLarge widget to my app, but I can't get Links to work. I want the user to be able to tap one of the four rows in my widget - like the EmojiRangers example - but I can't get it to work. I watched a Developer video from WWDC20: https://developer.apple.com/videos/play/wwdc2020/10036?time=223 The guy, Izzy, 'simply' embeds an HStack in a Link, and hey presto! It all works. But that doesn't happen for me. There's clearly some code in the background that runs. I already have .widgetURL working for .systemSmall and .systemMedium widgets, and I don't need to use Links on those two types. Those work by sending a URL to .onOpenURL { incomingURL in ... All good there, no issues. I've wrapped each row in the large widget in a Link with the URL of something like myappurlscheme://widgetTapped/widgetId (it's the same url as that used in the small and medium widgets). I build & run. I tap a row. It doesn't act as though a row is tappable (it doesn't go slightly transparent), and just opens the app without hitting .onOpenURL or anything else. Nothing in my scene delegate is triggered. Is there a specific delegate method that gets called? Do I need to set up some awful intents? I'm not using any sort of NavigationStack here; that model doesn't fit my app. Any ideas? Thanks.
1
0
540
Dec ’24
Handling non-consumable in-app purchase tiers
How would you go about handling this sort of situation? An app has two tiers of non-consumable in-app purchases. The IAP simply unlocks a certain level of access in the app: The first tier for $1.99 allows the user to add up to 50 things. The second tier for $3.99 allows the user to add up to 200 things. If the user has not bought an IAP the app will show the two tiers available for purchase. The user then buys Tier 1 and happily goes about adding some things to the app. The app now only shows Tier 2 available for purchase, because Tier 1 has been purchased. A few weeks go by and they realise they need to add more than 50 things. Would the user have to suck it up and just accept they should've paid the $3.99? Or, could a new Tier 1.5 be added that's a kind of upgrade price of $2.00 (the difference between the two original tiers) to unlock the higher 200 things level? I doubt this would work properly, because although I can control that tier being displayed or not in the app, I cannot control it in the App Store product pages, and it would be displayed among the Tier 1 and Tier 2 levels, so people would just buy that rather than the full priced Tier 2. How should I handle this situation? Just have the one tier (Tier 2) and make it simpler?
1
0
375
Jan ’25
In-app purchases - why so frustrating?
I'm adding my first in-app purchase to an app, and I'm finding the process incredibly frustrating. Aside from the Apple Developer Documentation not being clear enough, and kind of glossing over the technical steps required (and their sample code being woefully inadequate), App Store Connect and the testing sandbox simply don't work as they say they do. For example, in my app I've purchased the IAP and now I want to request a refund. I select the purchase, I choose a refund reason, and this page says, "To set up a test for approved refunds, select any refund reason on the refund request sheet, and submit the sheet. The App Store automatically approves the refund request in the testing environment." Well, when I re-launch the app the purchase is still there. I can't request a refund again because it says this is a duplicate refund request, so it knows that the purchase has had a request, and it's supposed to have automatically refunded it, but it clearly hasn't. So, I try clearing the purchase history via the Settings app > Developer > Sandbox Apple Account. Same thing. Purchase remains. Try clearing purchase history in App Store Connect. Same thing. How on Earth does anyone get an in-app purchase to work when the entire testing environment is so badly executed? How do I get past this? The IAP is the last part of the app that needs to be implemented, and I've lost a week on this already.
1
0
502
Feb ’25
Sandbox environment extremely unreliable
I have two sandbox users in App Store Connect, as I'm trying to test in-app purchases and Family Sharing. They're set up fine; I can make purchases in the app. The issue is that the refund request sheet in my app sometimes shows properly and lets me request a refund, but I'd say >80% of the time the sheet just shows "Cannot Connect" with a "Retry" button. Hitting that button doesn't ever result in the sheet showing the refund page. The only fix for this is to delete the app from the device, and restart the device. This has to be a joke, right? I need to be able to test this IAP, and the sandbox environment is useless most of the time. Why? Anyone experiencing this sort of issue?
1
1
499
Jan ’25