Post

Replies

Boosts

Views

Activity

UICollectionView + UISearchController - How to achieve smooth scrolling animation when hiding search bar?
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.
0
1
594
Nov ’23
WidgetKit iOS 17 : Interactive widget no longer responsive after a long idle time
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.
2
1
1k
Nov ’23
In iOS 17 AppIntent, how we can perform dynamic data selection for Widget?
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.
0
1
724
Nov ’23
UISheetPresentationController: Remove Dim Background but Keep Touch-to-Dismiss?
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.
1
0
1.4k
Oct ’23
What kind of configuration is required, so that WKWebView able to render image files in AppGroup directory?
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.
0
0
622
Apr ’23
Throw NSRangeException from NSFetchedResultsController.performFetch after introducing UICollectionViewDiffableDataSource
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.
0
0
730
Mar ’23
Does anyone know how I can reset/ cancel my in-app purchase status from TestFlight account?
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?
1
1
846
Mar ’23
Providing in-note text search (UIFindInteraction) feature in iOS 15
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.
0
0
829
Feb ’23
What is a reliable way, to check user system locale is using 12-hours or 24-hours format?
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?
3
0
676
Feb ’23
Does anyone know how can we edit "Data Not Linked to You" only?
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?
0
0
988
Jan ’23
SwiftUI - How to get correct RGB value from theme aware named color?
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?
0
0
867
Dec ’22
Is there a way to override the theme of WidgetKit, so that we can retrieve correct named color in WidgetKit?
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.
1
0
755
Dec ’22
Is calling WidgetCenter.shared.reloadAllTimelines() everytime during sceneDidEnterBackground a good practice?
Our widget extension appearance, is dependent on the main app content. The only way for us to keep the widget(s) up-to-date, is to perform the following during app inactive. class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneDidEnterBackground(_ scene: UIScene) { // Refresh home widgets. WidgetCenter.shared.reloadAllTimelines() This is always called regardless whether there is widget being placed. I was wondering, is this consider a good practice? I do not wish to perform unnecessary operation, which will drain up user battery.
0
1
810
Dec ’22
How to avoid CoreData corruption when developing Share extension to CoreData in AppGroup?
We plan to develop a Share extension. The CoreData is storing its SQLite file in AppGroup folder. The share extension will run in a different process than main app's. Since share extension and main app are running in different process, both will have their own instance of CoreData. However, even there are multiple instances of CoreData in different processes, their underlying are pointing to a single same SQLite file. Under https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1 It mentions When you set up a shared container, the containing app—and each contained app extension that you allow to participate in data sharing—have read and write access to the shared container. To avoid data corruption, you must synchronize data accesses. Use Core Data, SQLite, or Posix locks to help coordinate data access in a shared container. But it isn't clear, what are detailed steps required to How can we synchronize access to CoreData among 2 processes? Who should responsible to consume relevant store change, and update the single token file? (https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes) Should it be main app, share extension or both? Thank you.
1
0
764
Nov ’22
Is there any static analysis tool which can help us detect memory leak cause by missing [weak self]?
I was wondering, is there any tool, which can help to detect memory leak caused by missing [weak self]. For instance, the following code contains memory leak issue caused by lack of [weak self] class ImagePageViewController: UIPageViewController { lazy var memoryLeak = UIAction( title: "memory_leak", image: nil ) { _ in print(">>>> \(self)") } } Is there any tool which can help us to prevent such issue? I have tried  https://github.com/realm/SwiftLint but it is not able to detect such.
1
0
592
Sep ’22