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?
Post
Replies
Boosts
Views
Activity
Our submitted binary app version has been approved by Apple reviewers. It's currently pending Developer Release.
However, our in-app purchase, which was submitted at the same time, is still under review.
Since the newly added in-app purchase hasn't been approved yet, we've decided to hold off on releasing the binary app version.
Should we just wait for Apple to approve the in-app purchase, or are there any other recommended actions?
Thank you.
I have an app which is in the app store since 2022.
It is an app with combined features of note taking, todo list and calendar.
My most recent app update is rejected with the following message.
Guideline 4.3(a) - Design - Spam
We noticed your app shares a similar binary, metadata, and/or concept as apps submitted to the App Store by other developers, with only minor differences.
Submitting similar or repackaged apps is a form of spam that creates clutter and makes it difficult for users to discover new apps.
Next Steps
Since we do not accept spam apps on the App Store, we encourage you to review your app concept and submit a unique app with distinct content and functionality.
Resources
Some factors that contribute to a spam rejection may include:
Submitting an app with the same source code or assets as other apps already submitted to the App Store
Creating and submitting multiple similar apps using a repackaged app template
Purchasing an app template with problematic code from a third party
Submitting several similar apps across multiple accounts
Learn more about our requirements to prevent spam in App Review Guideline 4.3(a).
Support
Reply to this message in your preferred language if you need assistance. If you need additional support, use the Contact Us module.
Consult with fellow developers and Apple engineers on the Apple Developer Forums.
Help improve the review process or identify a need for clarity in our policies by suggesting guideline changes.
I reply the reviewer with the following message.
Hi,
I am writing to appeal the rejection of my app, ***. I would like to emphasize the unique aspects of *** that distinguish it from other apps in the market:
Integrated Functionality: Unlike most apps that are standalone note-taking, to-do, or calendar apps, *** uniquely combines all three functions into a single app.
Extensive Calendar Features: *** includes a comprehensive calendar feature covering holidays in 250 countries and 3,680 states and provinces, a rare and valuable feature.
Chinese Lunar Calendar: Our app includes a Chinese Lunar Calendar, which is highly appreciated in the Taiwanese market, adding a culturally specific feature not commonly found in other apps.
Organizational Tools: *** allows unlimited tabs and colors for organizing notes, surpassing many competitors who offer limited color options.
Flexible Viewing and Sorting Modes: Users can choose from four viewing modes (Grid, Compact Grid, List, Compact List) and five sorting modes (Modified Time, Created Time, Alphabet, Color, Check, Reminder), enhancing the user experience by providing flexibility and customization.
No Character Limit: Our design supports notes of any length without character limitations, unlike many apps that impose restrictions.
Organized To-Do Lists: Completed to-do items are automatically moved to the bottom, keeping the list clean and organized, a feature that sets us apart from other apps.
Rich Attachments: *** supports unlimited picture attachments, drawing attachments, and voice recordings for notes, offering more versatility than many other apps.
Advanced Security: *** offers 3 different kinds of password lock mechanisms to protect note privacy, ensuring users have multiple options for securing their information.
Powerful Reminders: Our app includes highly customizable reminders that can repeat daily, weekly, monthly, or yearly, making it easy for users to stay on top of their tasks and appointments.
Due to the character limits of this form, I cannot list all the unique features of ***. Please refer to our product description for a comprehensive overview.
We are proud of our 5k user reviews with an average rating of 4.8, reflecting user satisfaction with ***'s unique features.
Here's are some of the recent reviews. (United states only. Please kindly contact me to obtain full list of the reviews)
by ??? – Jun 14, 2024
omg i cannot explain how much i love *** like unlike the boring notes app this one is actually fun to use and rlly easy and i rlly like the fonts, color, to do list, calandar that can remind you like every day, week, month, and year like omg?!?!? i love *** and this app is going in like my "I 💖U" folder like tysm for making *** idk what i would do without it like omg tysm i cant explain in words how much i love it💖💖💖💖💖💖💖💖💖💖💖💖💖💖
by ??? – May 12, 2024
I love it sooo much I give it a 5 because there is a password so no one can look
by ??? – Apr 30, 2024
Perfect app. All in one
by ??? – Mar 26, 2024
I just downloaded this app a few days ago and I love it!! 😃 Up til recently I ALWAYS used paper sticky notes that I would have on my desk - and the reason why is because I could see everything all at once at all times; it’s very convenient - but it also produces clutter and anxiety because every subject is all in one place. *** fixed that!! 😀 With tabs and the list or grid view mode, I can have more organization of subjects and still see almost everything all at once! I love it! 🤩 suggestion I would really love to have more format options for the text! 🙂 Like a button to indent, and be able to have sub notes with points, dashes, or numbers. It makes everything look more tidy and neat. Thank you for this wonderful app! 😆
I am available at 123-456-789 to discuss further and provide additional detail. Thank you.
Till to date, I haven't received reply from the reviewer.
May I know, what are some other actionable items I can do, so that I can get through "Guideline 4.3(a) - Design - Spam" app update rejection?
Thank you so much!
In my recent endeavor, I aimed to introduce new Fetch Index Elements to the Core Data model of my iOS application. To achieve this, I followed a process of lightweight migration, detailed as follows:
Navigate to Editor > Add Model Version to create a new version of the data model.
Name the new version with a sequential identifier (e.g., MyAppModelV3.xcdatamodel) based on the naming convention of previous models.
Select the newly created version, MyAppModelV3.xcdatamodel, as the active model.
Mark this new version as the "Current" model in the Xcode properties panel on the right.
In the new version of the model, MyAppModelV3.xcdatamodel, and add the new Fetch Index Elements there. Also, insert "v3" in the Versioning Hash Modifier field of affected entity, to indicate this modification.
Upon reflection, I realized that creating a new version of the xcdatamodel might not have been necessary for this particular case. However, it appears to have caused no adverse effects on the application's functionality.
During testing, I executed the application in a simulated environment, initially running an older version of the app to inspect the database content with SQLite DB Browser. I then upgraded to the latest app version to verify that the migration was successfully completed without causing any crashes. Throughout this testing phase, I employed the -com.apple.CoreData.MigrationDebug 1 flag to monitor all SQL operations, ensuring that indexes were appropriately dropped and recreated for the affected entity.
Following thorough testing, I deployed the update to production. The majority of users were able to upgrade to the new app version seamlessly. However, a small fraction reported crashes at startup, indicated by the following error message:
Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={NSUnderlyingError=0x2820ad3e0 {Error Domain=NSCocoaErrorDomain Code=134100 "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." UserInfo={metadata={ NSPersistenceFrameworkVersion = 1338; NSStoreModelVersionChecksumKey = "qcPf6+DfpsPrDQ3j1EVXcBIrFe1O0R6IKd30sJf4IrI="; NSStoreModelVersionHashes = { NSAttachment = {length = 32, ...
Strangely, the only way I could replicate this issue in the simulator was by running the latest version of the app followed by reverting to an older version, a scenario unlikely to occur in a real-world setting. This raises the question: How could this situation arise with actual users, considering they would typically move from an old to a new version rather than the reverse?
I am reaching out to the community for insights or advice on this matter. Has anyone else encountered a similar problem during the Core Data migration process? How did you resolve it?
I wish to login simulator using a sandbox account so that I can test on in-app purchase and subscription.
I have created a new apple id account using a new email.
I have confirmed the new apple id + password is correct, by login into https://appleid.apple.com/#!&page=signin
I also added the new apple id account into App Store Connect as sandbox tester -
However, I am still not able login to simulator. I am keep getting error - "Username or password is incorrect"
You can see in the simulator background. I can login to https://appleid.apple.com/#!&page=signin using the new apple id + password via web browser. But, I am not sure why I am not able login into the simulator itself.
Can anyone assist me on this? Thank you.
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.
I have developed an interactive widget which looks as the following
It is using CoreData.
The view is implemented as the following
struct widget_extensionEntryView : View {
var body: some View {
if let nsTodoList = nsTodoList {
VStack(alignment: .leading, spacing: 1) {
...
ForEach(0..<prefixGoodNSTodoArray.count, id: \.self) { index in
...
let todoIntent = TodoIntent(objectIDAsString: objectIDAsString, parentOrder: parentOrder)
Button(intent: todoIntent) {
}
}
}
}
}
}
The AppIntent is implemented as the following
import Foundation
import AppIntents
import WidgetKit
import CoreData
struct TodoIntent: AppIntent {
static var title: LocalizedStringResource = "Complete Task"
static var description: IntentDescription = IntentDescription("Complete selected task")
@Parameter(title: "objectIDAsString")
var objectIDAsString: String
@Parameter(title: "parentOrder")
var parentOrder: Int
init() { }
init(objectIDAsString: String, parentOrder: Int) {
self.objectIDAsString = objectIDAsString
self.parentOrder = parentOrder
}
func perform() async throws -> some IntentResult {
guard let objectID = NSManagedObjectID.from(objectIDAsString) else { return .result() }
guard let nsTodoList = NSTodoListRepository.INSTANCE.getNSTodoList(objectID) else { return .result() }
nsTodoList.toggleChecked(context: CoreDataStack.INSTANCE.viewContext, parentOrder: Int64(parentOrder))
RepositoryUtils.saveContextIfPossible(CoreDataStack.INSTANCE.viewContext)
TodoWidgetOptions.isWrittenByWidgetExtension = true
// Refresh all home widgets.
// TODO: https://developer.apple.com/forums/thread/721937
WidgetCenter.shared.reloadAllTimelines()
return .result()
}
}
The interactive widget works pretty well.
However, tapping on the widget has no response in the following situations:
After an overnight, we turn on the iPhone's screen and tap on the widget.
After a few hours of idle time, we turn on the iPhone's screen and tap on the widget.
One of the steps below will make the widget workable again:
Launch and close the main app again. The main app will call WidgetCenter.shared.reloadAllTimelines() during sceneDidEnterBackground.
Press and hold on the widget, choose 'Edit widget', and select the desired Todo list.
One thing to take note of is that I am using IntentTimelineProvider instead of AppIntentTimelineProvider. The reason I am using 'older tech' is due to the limitation mentioned in https://developer.apple.com/forums/thread/741053
However, I am not sure whether this is the root cause of the problem.
Does anyone have any idea why such a problem occurs?
Thanks.
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'm using the UISheetPresentationController to present a view, as shown in the following code:
@IBAction func click(_ sender: Any) {
let whiteViewController = WhiteViewController.instanceFromMainStoryBoard()
if let sheet = whiteViewController.sheetPresentationController {
sheet.detents = [.medium()]
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersGrabberVisible = false
// Remove dim effect.
////sheet.largestUndimmedDetentIdentifier = .medium
}
self.present(whiteViewController, animated: true)
}
The outcome is as follow
This code results in a presentation with:
A dimmed background.
A 'Touch anywhere to dismiss' behavior.
However, I want to remove the dim background while keeping the 'Touch anywhere to dismiss' functionality. When I add the line:
sheet.largestUndimmedDetentIdentifier = .medium
The dim background is indeed removed, but the touch-to-dismiss behavior is gone too, as illustrated:
Is there a way to achieve both – removing the dim background and retaining the touch-to-dismiss functionality?
Thank you.
I have the following HTML string. We want to render image from our app AppGroup.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<title>This is title</title>
</head>
<body>
<p><h1>This is title</h1></p>
<div style="font-size: 0">
<img src="file:///Users/***/Library/Developer/CoreSimulator/Devices/A7B89802-9C65-4512-85A7-51C4372172D0/data/Containers/Shared/AppGroup/14DA3695-BFAF-4096-9F54-2874FD8285C2/attachment/b16c714e-9bb5-4eaa-924e-e043a69088ea.jpeg" width="100%">
</div>
This is body text
</body>
</html>
However, if we execute the following code
let html = ...
// wkWebView is WKWebView
wkWebView.loadHTMLString(html, baseURL: nil)
Only text is rendered. The image is not rendered.
May I know, what kind of configuration is required, so that WKWebView able to render image files in AppGroup directory?
Thanks.
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 install and test my own app, in real device, from TestFlight.
I want to able to keep testing on the in-app purchase/ subscription feature.
I notice once I have purchase an item success (I was prompted to enter password during purchase), it will forever be marked as purchased.
In simulator, if I uninstall then re-install again the app, the in-app purchase status will all be reset.
However, for TestFlight in real device, I do not find a way to reset my purchase. (So that I can keep testing on the purchase process)
Does anyone know, how I can reset/ cancel my in-app purchase status from TestFlight account?
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.
We would like to know, whether a user system locale is using 12-hours or 24-hours format?
There are many proposed solutions at https://stackoverflow.com/q/1929958/72437
One of the solutions are
let formatString = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)!
let hasAMPM = formatString.contains("a")
However, to me, this is not a correct solution.
When I tested with de_DE (German is using 24-hours), the returned string is HH 'Uhr'
What is Uhr mean for? I guess it mean "clock" in German?
There are so many other locales and one of them might contain letter a.
Does anyone know, what is a correct way, to check whether user system locale is using 12-hours or 24-hours format?
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?