Posts

Post not yet marked as solved
0 Replies
185 Views
In my recent endeavor, I aimed to introduce new Fetch Index Elements to the Core Data model of my iOS application. To achieve this, I followed a process of lightweight migration, detailed as follows: Navigate to Editor > Add Model Version to create a new version of the data model. Name the new version with a sequential identifier (e.g., MyAppModelV3.xcdatamodel) based on the naming convention of previous models. Select the newly created version, MyAppModelV3.xcdatamodel, as the active model. Mark this new version as the "Current" model in the Xcode properties panel on the right. In the new version of the model, MyAppModelV3.xcdatamodel, and add the new Fetch Index Elements there. Also, insert "v3" in the Versioning Hash Modifier field of affected entity, to indicate this modification. Upon reflection, I realized that creating a new version of the xcdatamodel might not have been necessary for this particular case. However, it appears to have caused no adverse effects on the application's functionality. During testing, I executed the application in a simulated environment, initially running an older version of the app to inspect the database content with SQLite DB Browser. I then upgraded to the latest app version to verify that the migration was successfully completed without causing any crashes. Throughout this testing phase, I employed the -com.apple.CoreData.MigrationDebug 1 flag to monitor all SQL operations, ensuring that indexes were appropriately dropped and recreated for the affected entity. Following thorough testing, I deployed the update to production. The majority of users were able to upgrade to the new app version seamlessly. However, a small fraction reported crashes at startup, indicated by the following error message: Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={NSUnderlyingError=0x2820ad3e0 {Error Domain=NSCocoaErrorDomain Code=134100 "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." UserInfo={metadata={ NSPersistenceFrameworkVersion = 1338; NSStoreModelVersionChecksumKey = "qcPf6+DfpsPrDQ3j1EVXcBIrFe1O0R6IKd30sJf4IrI="; NSStoreModelVersionHashes = { NSAttachment = {length = 32, ... Strangely, the only way I could replicate this issue in the simulator was by running the latest version of the app followed by reverting to an older version, a scenario unlikely to occur in a real-world setting. This raises the question: How could this situation arise with actual users, considering they would typically move from an old to a new version rather than the reverse? I am reaching out to the community for insights or advice on this matter. Has anyone else encountered a similar problem during the Core Data migration process? How did you resolve it?
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
216 Views
I wish to login simulator using a sandbox account so that I can test on in-app purchase and subscription. I have created a new apple id account using a new email. I have confirmed the new apple id + password is correct, by login into https://appleid.apple.com/#!&page=signin I also added the new apple id account into App Store Connect as sandbox tester - However, I am still not able login to simulator. I am keep getting error - "Username or password is incorrect" You can see in the simulator background. I can login to https://appleid.apple.com/#!&page=signin using the new apple id + password via web browser. But, I am not sure why I am not able login into the simulator itself. Can anyone assist me on this? Thank you.
Posted
by yccheok.
Last updated
.
Post marked as solved
2 Replies
1.9k Views
For PHPickerViewController, we know we can perform simple filtering by var config = PHPickerConfiguration() config.filter = PHPickerFilter.images But, how about we only want to show images with format JPG & PNG, but excluding GIF? This is because our app doesn't support GIF. Is it possible to do so?
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
318 Views
I notice that, normal implementation for UICollectionView + UISearchController, will not able to achieve smooth scrolling animation when hiding search bar. As you can see in the below video, when we scroll upward the page, it seems like "the page has slipped up suddenly". If we compare the animation with search bar in iOS Settings page, the problem is more obvious. iOS Settings page able to have a smooth scrolling experience. Implementation The following is our implementation. The complete workable project is found at : https://github.com/yccheok/demo-uicollectionview-uisearchcontroller class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate { @IBOutlet weak var collectionView: UICollectionView! let reuseIdentifier = "cell" // also enter this string as the cell identifier in the storyboard var items = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48","1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48","1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "bye"] private lazy var searchController: UISearchController = { let searchController = UISearchController(searchResultsController: UIViewController()) searchController.searchResultsUpdater = self searchController.obscuresBackgroundDuringPresentation = true searchController.searchBar.placeholder = "search_todos" searchController.searchBar.delegate = self return searchController }() override func viewDidLoad() { super.viewDidLoad() collectionView.dataSource = self collectionView.delegate = self navigationItem.searchController = searchController } // MARK: - UICollectionViewDataSource protocol // tell the collection view how many cells to make func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.items.count } // make a cell for each cell index path func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { // get a reference to our storyboard cell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MyCollectionViewCell // Use the outlet in our custom class to get a reference to the UILabel in the cell cell.label.text = self.items[indexPath.item] cell.backgroundColor = .yellow return cell } // MARK: - UICollectionViewDelegate protocol func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // handle tap events print("You selected cell #\(indexPath.item)!") } } class MyCollectionViewCell: UICollectionViewCell { @IBOutlet weak var label: UILabel! } extension ViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { } } extension ViewController: UISearchBarDelegate { } Do you have any idea, how we can achieve such a smooth scrolling animation? Thanks.
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
2 Replies
618 Views
I have developed an interactive widget which looks as the following It is using CoreData. The view is implemented as the following struct widget_extensionEntryView : View { var body: some View { if let nsTodoList = nsTodoList { VStack(alignment: .leading, spacing: 1) { ... ForEach(0..<prefixGoodNSTodoArray.count, id: \.self) { index in ... let todoIntent = TodoIntent(objectIDAsString: objectIDAsString, parentOrder: parentOrder) Button(intent: todoIntent) { } } } } } } The AppIntent is implemented as the following import Foundation import AppIntents import WidgetKit import CoreData struct TodoIntent: AppIntent { static var title: LocalizedStringResource = "Complete Task" static var description: IntentDescription = IntentDescription("Complete selected task") @Parameter(title: "objectIDAsString") var objectIDAsString: String @Parameter(title: "parentOrder") var parentOrder: Int init() { } init(objectIDAsString: String, parentOrder: Int) { self.objectIDAsString = objectIDAsString self.parentOrder = parentOrder } func perform() async throws -> some IntentResult { guard let objectID = NSManagedObjectID.from(objectIDAsString) else { return .result() } guard let nsTodoList = NSTodoListRepository.INSTANCE.getNSTodoList(objectID) else { return .result() } nsTodoList.toggleChecked(context: CoreDataStack.INSTANCE.viewContext, parentOrder: Int64(parentOrder)) RepositoryUtils.saveContextIfPossible(CoreDataStack.INSTANCE.viewContext) TodoWidgetOptions.isWrittenByWidgetExtension = true // Refresh all home widgets. // TODO: https://developer.apple.com/forums/thread/721937 WidgetCenter.shared.reloadAllTimelines() return .result() } } The interactive widget works pretty well. However, tapping on the widget has no response in the following situations: After an overnight, we turn on the iPhone's screen and tap on the widget. After a few hours of idle time, we turn on the iPhone's screen and tap on the widget. One of the steps below will make the widget workable again: Launch and close the main app again. The main app will call WidgetCenter.shared.reloadAllTimelines() during sceneDidEnterBackground. Press and hold on the widget, choose 'Edit widget', and select the desired Todo list. One thing to take note of is that I am using IntentTimelineProvider instead of AppIntentTimelineProvider. The reason I am using 'older tech' is due to the limitation mentioned in https://developer.apple.com/forums/thread/741053 However, I am not sure whether this is the root cause of the problem. Does anyone have any idea why such a problem occurs? Thanks.
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
425 Views
Before iOS17, when we implement "Edit widget" feature with dynamic data, we are using the following ways. Using an intent definition file Using an intent extension Here's the outcome. A searchable view controller Multi sectioned data view controller Here is the implementation intent definition file + intent extension class IntentHandler: INExtension, ConfigurationIntentHandling { func provideStickyNoteWidgetItemOptionsCollection(for intent: ConfigurationIntent, with completion: @escaping (INObjectCollection<StickyNoteWidgetItem>?, Error?) -> Void) { var stickyNoteWidgetItems = [StickyNoteWidgetItem]() var archivedStickyNoteWidgetItems = [StickyNoteWidgetItem]() let allStickyNoteWidgetItems = NSPlainNoteRepository.getStickyNoteWidgetItemsWithoutTrash() for allStickyNoteWidgetItem in allStickyNoteWidgetItems { if allStickyNoteWidgetItem.archived == 1 { archivedStickyNoteWidgetItems.append(allStickyNoteWidgetItem) } else { stickyNoteWidgetItems.append(allStickyNoteWidgetItem) } } var sections = [INObjectSection<StickyNoteWidgetItem>]() if !stickyNoteWidgetItems.isEmpty { let section = INObjectSection(title: nil, items: stickyNoteWidgetItems) sections.append(section) } if !archivedStickyNoteWidgetItems.isEmpty { let archivedSection = INObjectSection(title: "archive".localized, items: archivedStickyNoteWidgetItems) sections.append(archivedSection) } let collection = INObjectCollection(sections: sections) completion(collection, nil) } override func handler(for intent: INIntent) -> Any { // This is the default implementation. If you want different objects to handle different intents, // you can override this and return the handler you want for that particular intent. return self } } However, if I were using AppIntent in iOS17, I can only achieve the following Not searchable. Not section-able. Using AppIntent import Foundation import AppIntents @available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) struct StickyNoteWidgetItemAppEntity: AppEntity { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "StickyNoteWidgetItem") @Property(title: "archived") var archived: Bool? struct StickyNoteWidgetItemAppEntityQuery: EntityQuery { func entities(for identifiers: [StickyNoteWidgetItemAppEntity.ID]) async throws -> [StickyNoteWidgetItemAppEntity] { // TODO: return StickyNoteWidgetItemAppEntity entities with the specified identifiers here. return [] } func suggestedEntities() async throws -> [StickyNoteWidgetItemAppEntity] { // TODO: return likely StickyNoteWidgetItemAppEntity entities here. // This method is optional; the default implementation returns an empty array. return [ StickyNoteWidgetItemAppEntity(id: "id0", displayString: "note 0"), StickyNoteWidgetItemAppEntity(id: "id1", displayString: "note 1"), StickyNoteWidgetItemAppEntity(id: "id2", displayString: "note 2") ] } } static var defaultQuery = StickyNoteWidgetItemAppEntityQuery() var id: String // if your identifier is not a String, conform the entity to EntityIdentifierConvertible. var displayString: String var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(displayString)") } init(id: String, displayString: String) { self.id = id self.displayString = displayString } } By using AppIntent, I would like to achieve what is previously achievable using intent defination file + intent extension Is that ever possible? Thanks.
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
1 Replies
749 Views
I'm using the UISheetPresentationController to present a view, as shown in the following code: @IBAction func click(_ sender: Any) { let whiteViewController = WhiteViewController.instanceFromMainStoryBoard() if let sheet = whiteViewController.sheetPresentationController { sheet.detents = [.medium()] sheet.prefersScrollingExpandsWhenScrolledToEdge = false sheet.prefersGrabberVisible = false // Remove dim effect. ////sheet.largestUndimmedDetentIdentifier = .medium } self.present(whiteViewController, animated: true) } The outcome is as follow This code results in a presentation with: A dimmed background. A 'Touch anywhere to dismiss' behavior. However, I want to remove the dim background while keeping the 'Touch anywhere to dismiss' functionality. When I add the line: sheet.largestUndimmedDetentIdentifier = .medium The dim background is indeed removed, but the touch-to-dismiss behavior is gone too, as illustrated: Is there a way to achieve both – removing the dim background and retaining the touch-to-dismiss functionality? Thank you.
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
506 Views
I have the following HTML string. We want to render image from our app AppGroup. <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <title>This is title</title> </head> <body> <p><h1>This is title</h1></p> <div style="font-size: 0"> <img src="file:///Users/***/Library/Developer/CoreSimulator/Devices/A7B89802-9C65-4512-85A7-51C4372172D0/data/Containers/Shared/AppGroup/14DA3695-BFAF-4096-9F54-2874FD8285C2/attachment/b16c714e-9bb5-4eaa-924e-e043a69088ea.jpeg" width="100%"> </div> This is body text </body> </html> However, if we execute the following code let html = ... // wkWebView is WKWebView wkWebView.loadHTMLString(html, baseURL: nil) Only text is rendered. The image is not rendered. May I know, what kind of configuration is required, so that WKWebView able to render image files in AppGroup directory? Thanks.
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
616 Views
I was able to avoid NSInternalInconsistencyException by using NSDiffableDatasource with NSFetchedResultsController My data source definition is private typealias DataSource = UICollectionViewDiffableDataSource<Int, NSManagedObjectID> (Does anyone know why we should use Int as SectionIdentifierType? I find it works fine too, if I am using String as SectionIdentifierType) Even though I do not see NSInternalInconsistencyException anymore, I have the following crash log from Firebase Crashlytics. Do you know how to fix that? Fatal Exception: NSRangeException 0 CoreFoundation 0x99288 __exceptionPreprocess 1 libobjc.A.dylib 0x16744 objc_exception_throw 2 CoreFoundation 0x1a4318 -[__NSCFString characterAtIndex:].cold.1 3 CoreFoundation 0x928c8 -[NSArray subarrayWithRange:] 4 CoreData 0x702dc -[_PFArray subarrayWithRange:] 5 CoreData 0xc2d58 -[_NSDefaultSectionInfo objects] 6 CoreData 0x14eb98 -[NSFetchedResultsController _conditionallyDispatchSnapshotToDelegate:updatesInfo:] 7 CoreData 0xc3edc -[NSFetchedResultsController performFetch:] My NSFetchedResultsController look as following private lazy var fetchedResultsController: NSFetchedResultsController<NSPlainNote> = { let fetchRequest: NSFetchRequest<NSPlainNote> = NSPlainNote.fetchRequest( label: label, propertiesToFetch: ["pinned"] ) let controller = NSFetchedResultsController( fetchRequest: fetchRequest, managedObjectContext: CoreDataStack.INSTANCE.viewContext, sectionNameKeyPath: "pinned", cacheName: nil ) controller.delegate = fetchedResultsControllerDelegate return controller }() My NSPlainNote.fetchResult looks as following static func fetchRequest(label: String?, propertiesToFetch: [Any]?) -> NSFetchRequest<NSPlainNote> { let fetchRequest = NSPlainNote.fetchRequest() fetchRequest.propertiesToFetch = propertiesToFetch fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "pinned", ascending: false), NSSortDescriptor(key: "order", ascending: true) ] if let label = label { let predicate = NSPredicate(format: "archived = false AND trashed = false AND label = %@", label) fetchRequest.predicate = predicate } else { let predicate = NSPredicate(format: "archived = false AND trashed = false") fetchRequest.predicate = predicate } return fetchRequest } I am not able to reproduce the problem. However, we can observe crash happens during NSFetchedResultsController.performFetch. For those who are experience in this, do you know what is the root cause, and how I can resolve such? Thanks.
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
696 Views
I install and test my own app, in real device, from TestFlight. I want to able to keep testing on the in-app purchase/ subscription feature. I notice once I have purchase an item success (I was prompted to enter password during purchase), it will forever be marked as purchased. In simulator, if I uninstall then re-install again the app, the in-app purchase status will all be reset. However, for TestFlight in real device, I do not find a way to reset my purchase. (So that I can keep testing on the purchase process) Does anyone know, how I can reset/ cancel my in-app purchase status from TestFlight account?
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
732 Views
I would like to implement in-note text search feature, as found in Apple's Notes, Apple's Safari app. It looks like the following I understand that such an API is called UIFindInteraction, and only available in iOS16. https://developer.apple.com/documentation/uikit/uifindinteraction WWDC 2022: Adopt desktop-class editing interactions However, my app is targeting iOS 15. I was wondering, is it possible for us to provide same feature, in iOS 15? Thank you.
Posted
by yccheok.
Last updated
.
Post marked as solved
3 Replies
497 Views
We would like to know, whether a user system locale is using 12-hours or 24-hours format? There are many proposed solutions at https://stackoverflow.com/q/1929958/72437 One of the solutions are let formatString = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)! let hasAMPM = formatString.contains("a") However, to me, this is not a correct solution. When I tested with de_DE (German is using 24-hours), the returned string is HH 'Uhr' What is Uhr mean for? I guess it mean "clock" in German? There are so many other locales and one of them might contain letter a. Does anyone know, what is a correct way, to check whether user system locale is using 12-hours or 24-hours format?
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
904 Views
My app starts to introduce AdMob. In App Store Connect page, when I perform editing under App Privacy/ Data Types The input choice will be applicable to both "Data Used to Track You" and "Data Not Linked to You" Does anyone know how we can only edit "Data Not Linked to You" only? Am I doing something not right? Also, I notice that I am not allow to uncheck "Device ID" and then submit (For testing purpose). Is it because I am using NSUserTrackingUsageDescription in my app?
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
751 Views
The following is a code example from widget extension. By using .environment(.colorScheme, ...), I am able to update view with correct theme aware named color. However, I am not able to retrieve the correct RGB value, from the theme aware named color. private func getColorScheme() -> ColorScheme { if ... { return ColorScheme.dark } else { return ColorScheme.light } } @ViewBuilder func contentView() -> some View { // Light/ dark theme aware let color = SwiftUI.Color("yellowNoteColor") calculate(color) HStack { ... } .background(color) .environment(\.colorScheme, getColorScheme()) } func calculate(_ color: SwiftUI.Color) { var a: CGFloat = 0.0 var r: CGFloat = 0.0 var g: CGFloat = 0.0 var b: CGFloat = 0.0 let uiColor = UIColor(self) uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) // !!! Always get the light theme color value !!! } Inside calculate function, the retrieved value is always in the light theme color value. My guess is, caculate function is executed before .environment(\.colorScheme, getColorScheme()), that's why we are getting light theme color value always. May I know, how to get correct RGB value from theme aware named color?
Posted
by yccheok.
Last updated
.
Post not yet marked as solved
0 Replies
635 Views
In main app, I can override dark/ light theme based on user preference, so that I can retrieve correct color information based on named color. if (user preference) { overrideUserInterfaceStyle = .light } else { overrideUserInterfaceStyle = .dark } // Dark theme/ light theme automatic aware color. SwiftUI.Color("purpleColor") However, how can I override the theme of a WidgetKit, so that my WidgetKit can interpret named color correctly? I know in WidgetKit, I can read what is current system wide theme settings using  @Environment(\.colorScheme) But, that is not what I want. I want the ability to override theme of a WidgetKit based on user preference, then able to retrieve correct named color. Thanks.
Posted
by yccheok.
Last updated
.