I am using the following mechanism, to perform UITableView's row show and hide.
class TableViewController: UITableViewController {
private var hiddenIndexPaths : Set<IndexPath> = []
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func toggle(_ sender: UISwitch) {
if sender.isOn {
show(1)
show(2)
} else {
hide(1)
hide(2)
}
tableView.beginUpdates()
tableView.endUpdates()
}
private func isHidden(_ indexPath: IndexPath) -> Bool {
hiddenIndexPaths.contains(indexPath)
}
private func hide(_ item: Int) {
hiddenIndexPaths.insert(IndexPath(item: item, section: 0))
}
private func show(_ item: Int) {
hiddenIndexPaths.remove(IndexPath(item: item, section: 0))
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isHidden(indexPath) {
return 0.0
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
As you can see, it works great in iPhone SE simulator (Works well in iPhone SE real device too)
iPhone SE simulator
linkText
However, in non iPhone SE simulator (Like iPhone 13), once the table row is hidden, it can no longer be shown. Please refer to the following video.
iPhone 13 simulator
I am not sure what will its behavior in iPhone 13 real device, because I do not have access.
I was wondering, do you have any idea why such issue occurs?
If you are interested to test, here's the complete workable sample - https://github.com/yccheok/show-hide-table-row-bug
Post
Replies
Boosts
Views
Activity
I am using XCode 14.
The following is the UIImageView, using SFSymbol face.smiling
UIImageView background is pink color
UIImageView tint is black color
When I switch my app to dark mode, the smiling face which use to be transparent color, has became solid black color.
The face line (eye, mouth, face border) used to be solid black color, had became transparent color.
It seems like the image has changed from "face.smiling" to "face.smiling.fill" ?
I would like to avoid such outcome. Is there a way, to force UIImageView load SFSymbol in light mode, even though the entire app is using dark mode?
(This happens same when I test using real device and simulator)
Thanks.
My XCode 13+ was used at MacBook Pro (13-inch, M1, 2020, 16G RAM) without issue
Even since updated XCode to 14 week ago, I experience slowness while typing in XCode, and other editing operation.
and, this crash happens few times a day
Does anyone experience the same thing as I do?
Thanks.
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 ?
My users will redeem promo code via app store (outside my app)
Currently, I am using StoreKit2 to
Query Transaction.currentEntitlements during app startup
Keep listening to Transaction.updates
I was wondering, is that is so, is there any special code required, to
handle Promo code redeem from user?
I guess by keep listening to Transaction.updates, will able to handle such case? But, I am not able to test that, without rolling out my app to production.
Thanks.
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.
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.
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
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.
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.
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()
}
}
}
}
}
Every of our data row, contains an unique uuid column.
Previously, before adopting CloudKit, the uuid column has a unique constraint. This enables us to prevent data duplication.
Now, we start to integrate CloudKit, into our existing CoreData. Such unique constraint is removed. The following user flow, will cause data duplication.
Steps to cause data duplication when using CloudKit
Launch the app for the first time.
Since there is empty data, a pre-defined data with pre-defined uuid is generated.
The pre-defined data is sync to iCloud.
The app is uninstalled.
The app is re-installed.
Launch the app for the first time.
Since there is empty data, a pre-defined data with pre-defined uuid is generated.
Previous old pre-defined data from step 3, is sync to the device.
We are now having 2 pre-defined data with same uuid! :(
I was wondering, is there a way for us to prevent such duplication?
In step 8, we wish we have a way to execute such logic before written into CoreData
Check whether such uuid exists in CoreData. If not, write to CoreData.
If not, we will pick the one with latest update date, then overwrite
the existing data.
I once try to insert the above logic into https://developer.apple.com/documentation/coredata/nsmanagedobject/1506209-willsave . To prevent save, I am using self.managedObjectContext?.rollback(). But it just crash.
Do you have any idea, what are some reliable mechanism I can use, to prevent data duplication in CoreData CloudKit?
Additional info:
Before adopting CloudKit
We are using using the following CoreData stack
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
precondition(Thread.isMainThread)
let container = NSPersistentContainer(name: "***", managedObjectModel: NSManagedObjectModel.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
}()
Our CoreData data schema has
Unique constraint.
Deny deletion rule for relationship.
Not having default value for non-null field.
After adopting CloudKit
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
precondition(Thread.isMainThread)
let container = NSPersistentCloudKitContainer(name: "***", managedObjectModel: NSManagedObjectModel.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
}()
We change the CoreData data schema to
Not having unique constraint.
Nullify deletion rule for relationship.
Having default value for non-null field.
Based on a feedback of a Developer Technical Support engineer from https://developer.apple.com/forums/thread/699634?login=true , hen mentioned we can
Detecting Relevant Changes by Consuming Store Persistent History
Removing Duplicate Data
But, it isn't entirely clear on how it should be implemented, as the github link provided in broken.
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?
We start a voice recording via
self.avAudioRecorder = try AVAudioRecorder(
url: self.recordingFileUrl,
settings: settings
)
self.avAudioRecorder.record()
At certain point, we will stop the recording via
self.avAudioRecorder.stop()
I was wondering, is it safe to perform file copy on self.recordingFileUrl immediately, after self.avAudioRecorder.stop()?
Is all recording data has been flushed to self.recordingFileUrl and self.recordingFileUrl file is closed properly?
Since StoreKit2
Transaction.currentEntitlements
will able to return us user current owned purchased, during app startup.
If that is the case, is it still necessary for developer to provide a restore button?
If we still need to provide a restore button, what should the restore button do and what API should it call?
Thanks