I have the following top status bar hide/ show code snippet with animation. It works well under iOS 14.5
private var additionalHeight: CGFloat {
if UIDevice.current.hasNotch {
return 0
} else {
return self.navigationController?.view.safeAreaInsets.top ?? 0
}
}
private var statusBarHidden: Bool = false {
didSet {
if statusBarHidden {
self.navigationController?.additionalSafeAreaInsets.top = additionalHeight
} else {
self.navigationController?.additionalSafeAreaInsets.top = 0
}
UIView.animate(withDuration: Constants.config_shortAnimTime) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
// https://medium.com/@badlands/unfortunately-this-also-applies-to-ipad-pro-which-have-non-zero-safeareainsets-e1aa0d002462
extension UIDevice {
/// Returns 'true' if the current device has a notch
var hasNotch: Bool {
if #available(iOS 11.0, *) {
// https://stackoverflow.com/a/57899013/72437
let keyWindow = UIWindow.key
// Case 1: Portrait && top safe area inset >= 44
let case1 = !UIDevice.current.orientation.isLandscape && (keyWindow?.safeAreaInsets.top ?? 0) >= 44
// Case 2: Lanscape && left/right safe area inset > 0
let case2 = UIDevice.current.orientation.isLandscape && ((keyWindow?.safeAreaInsets.left ?? 0) > 0 || (keyWindow?.safeAreaInsets.right ?? 0) > 0)
return case1 || case2
} else {
// Fallback on earlier versions
return false
}
}
}
iOS 14.5
However, when same code runs in iOS 15, the animation is broken. We use latest XCode 13.0
During hide, there is no more "slide up" hide animation.
During show, it will try to "push down" the entire view controller.
iOS 15
Does anyone has idea how to fix such animation in iOS 15, so that it behaves similar as iOS 14.5 ?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I want to start learning to use NSAsynchronousFetchRequest by referring to
https://www.marcosantadev.com/coredata_crud_concurrency_swift_2/
I have the simplest use case of NSAsynchronousFetchRequest
NSAsynchronousFetchRequest
// Call from UI main thread
func X() {
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
guard let result = asynchronousFetchResult.finalResult as? [NSPlainNote] else { return }
}
let coreDataStack = CoreDataStack.INSTANCE
// backgroundContext created via persistentContainer.newBackgroundContext()
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
do {
try backgroundContext.execute(asynchronousFetchRequest)
} catch let error {
backgroundContext.rollback()
error_log(error)
}
}
}
However, running the above code will get me the following error
CoreData`+[NSManagedObjectContextMultithreading_Violation_AllThatIsLeftToUsIsHonor]:
If I modify the code by using NSFetchRequest directly.
NSFetchRequest
// Call from UI main thread
func X() {
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
let coreDataStack = CoreDataStack.INSTANCE
// backgroundContext created via persistentContainer.newBackgroundContext()
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
do {
let nsPlainNotes = try fetchRequest.execute()
} catch let error {
backgroundContext.rollback()
error_log(error)
}
}
}
Thing works fine. May I know, what's wrong with my NSAsynchronousFetchRequest version of code?
This is my CoreDataStack.swift for reference purpose.
CoreDataStack.swift
import CoreData
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "wenote")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
// TODO: Not sure these are required...
//
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
// Similar behavior as Android's Room OnConflictStrategy.REPLACE
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// TODO: Not sure these are required...
//backgroundContext.undoManager = nil
return backgroundContext
}()
}
Additional information
Do note that, in NSAsynchronousFetchRequest example, even if backgroundContext.perform is not used.
// Call from UI main thread
func X() {
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
guard let result = asynchronousFetchResult.finalResult as? [NSPlainNote] else { return }
}
let coreDataStack = CoreDataStack.INSTANCE
// backgroundContext created via persistentContainer.newBackgroundContext()
let backgroundContext = coreDataStack.backgroundContext
do {
try backgroundContext.execute(asynchronousFetchRequest)
} catch let error {
backgroundContext.rollback()
error_log(error)
}
}
Same fatal error still occur.
Please note that, this fatal error will only be triggered, by editing the schema with Arguments Passed On Launch
-com.apple.CoreData.ConcurrencyDebug 1
I even try to execute some simple project from
https://github.com/abhishekbedi1432/Core-Data-Asynchronous-Fetching/tree/master
which is using NSAsynchronousFetchRequest.
If I do not enable -com.apple.CoreData.ConcurrencyDebug 1, the sample project from github able to perform asynchronous fetch without issue. However, once the -com.apple.CoreData.ConcurrencyDebug 1 is enabled, it will also be getting the same fatal error.
It seems there are 2 ways to perform asynchronous read in CoreData, without blocking main thread UI.
newBackgroundContext + FetchRequest
Source : https://www.advancedswift.com/core-data-background-fetch-save-create/
// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()
// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true
// Perform operations on the background context
// asynchronously
context.perform {
do {
// Create a fetch request
let fetchRequest: NSFetchRequest<CustomEntity>
fetchRequest = CustomEntity.fetchRequest()
fetchRequest.fetchLimit = 1
let objects = try context.fetch(fetchRequest)
// Handle fetched objects
}
catch let error {
// Handle error
}
}
newBackgroundContext + NSAsynchronousFetchRequest
Source: https://www.marcosantadev.com/coredata_crud_concurrency_swift_2/
let privateManagedObjectContext = persistentContainer.newBackgroundContext()
// Creates a fetch request to get all the dogs saved
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// Creates `asynchronousFetchRequest` with the fetch request and the completion closure
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
// Retrieves an array of dogs from the fetch result `finalResult`
guard let result = asynchronousFetchResult.finalResult as? [Dog] else { return }
// Dispatches to use the data in the main queue
DispatchQueue.main.async {
// Do something
}
}
do {
// Executes `asynchronousFetchRequest`
try privateManagedObjectContext.execute(asynchronousFetchRequest)
} catch let error {
print("NSAsynchronousFetchRequest error: \(error)")
}
However, note that, the above code will unfortunately cause fatal error, if I were to enable flag -com.apple.CoreData.ConcurrencyDebug 1.
So far, I do not have a good solution to such. For more detail, please refer to https://developer.apple.com/forums/thread/697718 ?
May I know, what is the difference among newBackgroundContext + FetchRequest vs newBackgroundContext + NSAsynchronousFetchRequest?
How should I choose one over another? Thank you.
We wish to implement in-app purchase for our app, which is currently targeting 14.3
The latest StoreKit 2, is only available for iOS 15
We wish to study how should we implement such, by looking at official code example of StoreKit 1.
However, the current posted code at Apple site, is only available for StoreKit 2 - https://developer.apple.com/documentation/storekit/in-app_purchase/implementing_a_store_in_your_app_using_the_storekit_api
Does anyone have any idea, where can I get the official StoreKit 1 code example, from Apple?
Thanks.
In iOS15, we have an efficient way to update items cell, by using reconfigureItems.
Here's the code snippet to perform such efficient update.
Update items cell efficiently using reconfigureItems
private func reconfigureRecordingRow(_ recording: Recording) {
var snapshot = dataSource.snapshot()
snapshot.reconfigureItems([recording])
dataSource.apply(snapshot)
}
private func makeDataSource() -> DataSource {
let dataSource = DataSource(
collectionView: collectionView,
cellProvider: { [weak self] (collectionView, indexPath, anyHashable) -> UICollectionViewCell? in
guard let self = self else { return nil }
guard let recordingCell = collectionView.dequeueReusableCell(
withReuseIdentifier: "recording",
for: indexPath) as? RecordingCell else {
return nil
}
When reconfigureRecordingRow is called, cellProvider's function will be executed.
collectionView.dequeueReusableCell is able to re-use existing UICollectionViewCell, without constructing new UICollectionViewCell
However, I was wondering, how can I achieve a similar efficiency, if I have a section, with header supplementary view, and without any item? For instance
Not able to update supplementary view efficiently
private func reloadAttachmentRow() {
var snapshot = dataSource.snapshot()
let sectionIdentifiers = snapshot.sectionIdentifiers
if sectionIdentifiers.contains(.attachment) {
snapshot.reloadSections([.attachment])
} else {
snapshot.insertSections([.attachment], beforeSection: .title)
}
dataSource.apply(snapshot)
}
dataSource.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
guard let self = self else { return nil }
if kind == UICollectionView.elementKindSectionHeader {
let section = indexPath.section
let sectionIdentifier = self.sectionIdentifier(section)
switch sectionIdentifier {
case .attachment:
guard let collageViewHeader = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: "attachment",
for: indexPath) as? CollageViewHeader else {
return nil
}
When reloadSections is called, dataSource.supplementaryViewProvider will be executed.
As per my testing, collectionView.dequeueReusableSupplementaryView will return a new instance of UICollectionReusableView each time.
As a result, I can visually observe the entire section is "flickering", when reloadAttachmentRow is called.
I was wondering, how can we update supplementary view efficiently?
I'm using NSMetadataQuery and NotificationCenter, to perform file downloading from iCloud.
Construct NSMetadataQuery with predicate NSPredicate(format: "%K == %@", NSMetadataItemFSNameKey, filename)
Observe NSMetadataQueryDidUpdate & NSMetadataQueryDidFinishGathering using NotificationCenter.
Check file status NSMetadataUbiquitousItemDownloadingStatusKey. If the file is up-to-date, copy the file to destination directory, and jump to step 6.
Perform FileManager.default.startDownloadingUbiquitousItem
Receive file downloading status in NSMetadataQueryDidUpdate callback. If the file is up-to-date, copy the file to destination directory, and jump to step 6.
Perform cleanup by removing all observers.
If file is not available in iCloud, no notification received.
We wish, even if the file doesn't exist, we will still be notified, so that we have chance to perform cleanup (step 6)
Here's the code snippet to perform iCloud download.
DownloadManager.swift
class DownloadManager {
static let INSTANCE = DownloadManager()
var downloaders = [iCloudDocumentDownloader]()
private init() {
}
func append(filename: String, destinationDirectory: URL) {
let downloader = iCloudDocumentDownloader(filename: filename, destinationDirectory: destinationDirectory)
downloaders.append(downloader)
}
func removeAll(_ downloader: iCloudDocumentDownloader) {
downloaders.removeAll{$0 === downloader}
}
}
iCloudDocumentDownloader.swift
class iCloudDocumentDownloader {
private let filename: String
private let destinationDirectory: URL
private let metadataQuery = NSMetadataQuery()
private static let operationQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.yocto.wenote.operationQueueForiCloudDocument"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.qualityOfService = .userInitiated
return operationQueue
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
private func bye() {
DownloadManager.INSTANCE.removeAll(self)
}
init(filename: String, destinationDirectory: URL) {
self.filename = filename
self.destinationDirectory = destinationDirectory
metadataQuery.operationQueue = iCloudDocumentDownloader.operationQueue
metadataQuery.predicate = NSPredicate(format: "%K == %@", NSMetadataItemFSNameKey, filename)
metadataQuery.searchScopes = [
NSMetadataQueryUbiquitousDocumentsScope
]
NotificationCenter.default.addObserver(self, selector: #selector(didUpdate), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: metadataQuery)
NotificationCenter.default.addObserver(self, selector: #selector(didFinishGathering), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: metadataQuery)
metadataQuery.start()
}
@objc func didUpdate(_ notification: Notification) {
guard let metadataQuery = notification.object as? NSMetadataQuery else { return }
metadataQuery.enumerateResults { [weak self] (item: Any, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
guard let self = self else { return }
guard let metadataItem = item as? NSMetadataItem else { return }
guard let status = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String else { return }
guard let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL else { return }
if status == NSMetadataUbiquitousItemDownloadingStatusCurrent {
if !destinationDirectory.createCompleteDirectoryHierarchyIfDoesNotExist() {
self.bye()
// Early return.
return
}
let destinationURL = destinationDirectory.appendingPathComponent(filename, isDirectory: false)
do {
try FileManager.default.copyItem(at: url, to: destinationURL)
} catch {
error_log(error)
}
self.bye()
} else if let error = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingErrorKey) as? NSError {
error_log(error)
self.bye()
} else {
}
}
}
@objc func didFinishGathering(_ notification: Notification) {
guard let metadataQuery = notification.object as? NSMetadataQuery else { return }
metadataQuery.enumerateResults { [weak self] (item: Any, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
guard let self = self else { return }
guard let metadataItem = item as? NSMetadataItem else { return }
guard let status = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String else { return }
guard let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL else { return }
if status == NSMetadataUbiquitousItemDownloadingStatusCurrent {
if !destinationDirectory.createCompleteDirectoryHierarchyIfDoesNotExist() {
self.bye()
// Early return.
return
}
let destinationURL = destinationDirectory.appendingPathComponent(filename, isDirectory: false)
do {
try FileManager.default.copyItem(at: url, to: destinationURL)
} catch {
error_log(error)
}
self.bye()
} else if let error = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingErrorKey) as? NSError {
error_log(error)
self.bye()
} else {
do {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
} catch {
error_log(error)
self.bye()
}
}
}
}
}
Due to privacy concern, I believe some users prefer their data not to sync with iCloud.
Hence, we plan to provide a switch option within the app, so that user can choose, whether to sync their data with iCloud.
Such feature can be achieved via the implementation suggested at https://developer.apple.com/forums/thread/118924?login=true
However, once user chooses to disable sync with iCloud, we should not perform purging on history tracking transactions.
Reason is that, if few months later/ few years later, user decides to turn on sync with iCloud again, lack of sufficient history tracking transactions, will cause sync with iCloud operation fail.
This is causing a dilemma. If we never clean the history tracking transactions, will it cause disk full issue?
If that is so, may I know, what is the correct way, to handle history tracking transactions purging, if we intent to provide CloutKit sync enable/ disable feature?
Thank you.
Due to privacy concern, we wish to provide a toggle switch, so that user has the freedom, to choose whether to sync CoreData with iCloud.
We can implement such a feature by following suggestion from https://developer.apple.com/forums/thread/118924?login=true
However, we should not perform history tracking transactions purging, once user disables CloudKit sync
Reason is that, if few months later/ few years later, user decides to turn on CloudKit sync again, lack of sufficient history tracking transactions, will cause CloudKit sync fail.
This is causing a dilemma. If we never clean the history tracking transactions, will it cause disk full issue?
If that is so, may I know, what is the correct way to handle history tracking transactions purging, if we allow users to enable/ disable CloudKit sync?
Thank you.
After using CloudKit enabled CoreData app for a while, I have the following observation.
I have been actively use a CloudKit enabled CoreData app, for 2 weeks.
Then, I get another fresh device. Once I hook up the fresh device to internet, based on the UI updating sequence, it seems like CoreData is replaying the entire transaction history, starting from 2 weeks ago till now.
So, my questions is, if we are using the CloudKit enabled CoreData app for 5 years.
Then, when I get a fresh new device, will the CoreData replaying the entire transaction history starting from 5 years ago?
Isn't that is highly inefficient, and cause the new device sync extremely slow? Is that is so, how can we avoid such slowness, when user is using the app long time enough, and then decide to sync to a newly bought device?
Thank you
We notice PHPickerConfiguration will produce wrong ordering sometimes, during multi selection.
Our app is targeting iOS 15, and the test is run on simulator iOS 15.5
Here the video to demonstrate the issue
https://youtu.be/RrhqFuB2kqs
Please note that, the standing cat picture suppose to be the 4th order.
By looking at the console and UI outcome, it is mistakenly returned as 3rd order.
Here's the code which show
func chooseImage() {
var configuration = PHPickerConfiguration()
configuration.selectionLimit = 0
// https://developer.apple.com/documentation/photokit/phpickerconfiguration/selection
configuration.selection = .ordered
// GIF exclusion is still not supported - https://developer.apple.com/forums/thread/687415
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
and here's the code which prints out the ordering of images.
extension NewNoteViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard !results.isEmpty else { return }
var displayErrorOnce = false
for result in results {
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { [weak self] (url, error) in
precondition(!Thread.isMainThread)
guard let self = self else { return }
guard let url = url else { return }
print(">>>> url \(url)")
// ...
}
}
}
}
This problem happens randomly.
May I know, is there any workaround for this issue?
Thank you.
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.
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 ?
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.
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?
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?