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.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
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?
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.
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.
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.
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.
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.
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?
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.
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.
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.
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