Post

Replies

Boosts

Views

Activity

Can anyone share your experience in migrating CloudKit enabled CoreData to AppGroup to support Widget?
Our current app is using CloudKit enabled CoreData. Recently, we want to support Widget feature. We understand that in order to support Widget feature, we need to place our CoreData under AppGroup, so that the data can be accessed by Widget. May I know, is there any good guideline on how to do so, so that we would not cause data loss for existing users? Thank you.
0
0
600
Sep ’22
CoreData generated class - Is there memory leak risk of not using weak in inverse relationship?
When come to circular reference, it comes with risk of memory leaking, by not using a weakkeyword. For instance :- Memory leak without using weak class Human { deinit { print("bye bye from Human") } init(_ pet: Pet) { self.pet = pet } let pet: Pet } class Pet { deinit { print("bye bye from Pet") } var human: Human? } print("start of scope") if true { let pet = Pet() let human = Human(pet) pet.human = human print("going to end of scope") } print("end of scope") /* Output: start of scope going to end of scope end of scope */ No memory leak by using weak class Human { deinit { print("bye bye from Human") } init(_ pet: Pet) { self.pet = pet } let pet: Pet } class Pet { deinit { print("bye bye from Pet") } weak var human: Human? } print("start of scope") if true { let pet = Pet() let human = Human(pet) pet.human = human print("going to end of scope") } print("end of scope") /* Output: start of scope going to end of scope bye bye from Human bye bye from Pet end of scope */ In CoreData, when setup 2 entities with one-to-many relationship, it is recommended to have inverse relationship too. Hence, CoreData will generate the following class with circular reference. extension NSHolidayCountry { @nonobjc public class func fetchRequest() -> NSFetchRequest<NSHolidayCountry> { return NSFetchRequest<NSHolidayCountry>(entityName: "NSHolidayCountry") } @NSManaged public var code: String @NSManaged public var name: String @NSManaged public var holidaySubdivisions: NSOrderedSet } extension NSHolidaySubdivision { @nonobjc public class func fetchRequest() -> NSFetchRequest<NSHolidaySubdivision> { return NSFetchRequest<NSHolidaySubdivision>(entityName: "NSHolidaySubdivision") } @NSManaged public var code: String @NSManaged public var name: String @NSManaged public var holidayCountry: NSHolidayCountry? } NSHolidaySubdivision is having inverse relationship to NSHolidayCountry. However, such inverse relationship is not marked as weak, based on CoreData generated class. I was wondering, does this come with a memory leak risk? Should I, add a weak keyword manually in entity NSHolidaySubdivision's holidayCountry ?
0
0
685
Sep ’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
971
Dec ’22
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
1k
Dec ’22
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
1.1k
Jan ’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
937
Feb ’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
852
Mar ’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/xxx/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.
Topic: Safari & Web SubTopic: General Tags:
0
0
708
Apr ’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
2
1k
Nov ’23
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.
Topic: UI Frameworks SubTopic: UIKit Tags:
0
1
844
Nov ’23
Implementing Undo/Redo Feature in UITextView with IME Support
Currently, we are implementing an undo/redo feature in UITextView. However, we cannot use the built-in UndoManager in UITextView because we have multiple UITextView instances inside a UICollectionView. Since UICollectionView recycles UITextView instances, the same UITextView might be reused in different rows, making the built-in UndoManager unreliable. The shouldChangeTextIn method in UITextViewDelegate is key to implementing undo/redo functionality properly. Here is an example of our implementation: extension ChecklistCell: UITextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { // Get the current text let s = textView.text ?? "" // Get the starting position of the change let start = range.location // Get the number of characters that will be replaced let count = range.length // Get the number of characters that will be added let after = text.count print(">>>> The current text = \"\(s)\"") print(">>>> The starting position of the change = \(start)") print(">>>> The number of characters that will be replaced = \(count)") print(">>>> The number of characters that will be added = \(after)") print(">>>>") if let delegate = delegate, let checklistId = checklistId, let index = delegate.checklistIdToIndex(checklistId) { delegate.attachTextAction(s: s, start: start, count: count, after: after, index: index) } return true } } Working scene behind the UITextViewDelegate However, this implementation does not work well with non-English input using an IME. When using an IME, there is an intermediate input before the final input is produced. For example, typing "wo" (intermediate input) produces "我" (final input). Currently, UITextViewDelegate captures both "wo" and "我". UITextViewDelegate captures both "wo" and "我" Is there a way to ignore the intermediate input from IME and only consider the final input? In Android, we use the beforeTextChanged method in TextWatcher to seamlessly ignore the intermediate input from IME and only consider the final input. You can see this in action in this Android captures only "我" Is there an equivalent way in iOS to ignore the intermediate input from IME and only take the final input into consideration?
Topic: UI Frameworks SubTopic: UIKit Tags:
0
0
549
Jun ’24
Customizing Drop Location View in UICollectionView Drag and Drop
Currently, this is how I implement the drag and move operation: collectionView.beginInteractiveMovementForItem collectionView.updateInteractiveMovementTargetPosition collectionView.endInteractiveMovement The outcome looks like the following: However, what I would like to achieve is the ability to customize the view of the "drop" location. For instance, in the following example, a red line is drawn at the target drop location: In this example, a transparent rectangle is drawn at the target drop location: May I know how these apps achieve such an effect? Thanks.
Topic: UI Frameworks SubTopic: UIKit Tags:
0
0
400
Jul ’24
When using NSCollectionLayoutSection.list, how to specific header height and cell item height?
I am using NSCollectionLayoutSection.list as follow. private func layoutConfig() -> UICollectionViewCompositionalLayout { let layout = UICollectionViewCompositionalLayout { section, layoutEnvironment in var config = UICollectionLayoutListConfiguration(appearance: .plain) config.headerMode = .supplementary config.footerMode = .none config.showsSeparators = false config.headerTopPadding = 0 // https://developer.apple.com/forums/thread/759987 let layoutSection = NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) layoutSection.interGroupSpacing = 0 layoutSection.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) if let header = layoutSection.boundarySupplementaryItems.first(where: { $0.elementKind == UICollectionView.elementKindSectionHeader }) { header.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) } return layoutSection } return layout } We provide our own custom header view and cell item view. Header View class HideShowHeader: UICollectionViewListCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize { // Ensure the cell fills the width of the collection view let size = CGSize( width: targetSize.width, height: 80 ) print(">>>> size \(size)") return size } } Cell Item View class TodoCell: UICollectionViewListCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize { // Ensure the cell fills the width of the collection view let size = CGSize( width: targetSize.width, height: 80 ) return size } } We would like to fine-tune the height of header and cell item. However, override systemLayoutSizeFitting doesn't work. May I know, when using NSCollectionLayoutSection.list, how to specific header height and cell item height? Thanks.
Topic: UI Frameworks SubTopic: UIKit Tags:
0
1
447
Jul ’24
Understanding Policies on Account Associations: Google vs. Apple
Google has a very strict policy regarding "associated previously terminated accounts." This means that if you are associated with another previously banned developer, you will be banned too. For instance : https://www.reddit.com/r/androiddev/comments/9mpyyi/google_play_developer_account_terminated_due_to/ Does Apple have a similar policy? Recently, I was considering providing read-only access to an external party on App Store Connect. However, I am concerned that it might trigger the same risk. Therefore, I am wondering if Apple has such a policy. Thanks.
0
0
487
Jul ’24
UILabel Attributed Text Anomaly: Unexpected Strikethrough When Setting Text Property in Swift
The following code, will create a red color text, without strike-through. class ViewController: UIViewController { @IBOutlet weak var label: UILabel! override func viewDidLoad() { super.viewDidLoad() let text = "Hello World" let textCount = text.count let fullRange = NSRange(location: 0, length: textCount) var attributedText = NSMutableAttributedString(string: text) attributedText.addAttribute(.foregroundColor, value: UIColor.green, range: fullRange) attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange) label.attributedText = attributedText attributedText = NSMutableAttributedString(string: text) attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange) attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange) label.attributedText = attributedText } } However, if I trigger label.text in between, it will cause the following strange behavior : A red color text, with strike-through created at the end of function. class ViewController: UIViewController { @IBOutlet weak var label: UILabel! override func viewDidLoad() { super.viewDidLoad() let text = "Hello World" let textCount = text.count let fullRange = NSRange(location: 0, length: textCount) var attributedText = NSMutableAttributedString(string: text) attributedText.addAttribute(.foregroundColor, value: UIColor.green, range: fullRange) attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange) label.attributedText = attributedText // Why this will cause a red color text, with strike-through created at the end of function? label.text = text attributedText = NSMutableAttributedString(string: text) attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange) attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange) label.attributedText = attributedText } } Does anyone what is the reason behind this behavior, and how I can avoid such? Thank you.
Topic: UI Frameworks SubTopic: UIKit
0
0
394
Aug ’24