Calling mergeChanges on a managed object context will automatically refresh any managed objects that have changed. This ensures that your context always contains all the latest information. Note that you don’t have to call mergeChanges on a viewContext when you set its automaticallyMergesChangesFromParent property to true. In that case, Core Data will handle the merge on your behalf.
While you can fetch data from Core Data with @FetchRequest just fine, I tend to avoid it in my apps. The main reason for this is that I’ve always tried to separate my Core Data code from the rest of my application as much as possible.
This can be misleading. When one see parent store he can understand the parent persitent store of my context hierarchy. However, what is meant by parent store is either: the persistentStoreCoordinator or the parentContext So, if your context was setup with a parent context, changes are commited to his parent but no further. To save it to the persistent store, you’ll need to call save() on contexts all the way up in the hierarchy until you reach the persistent store.
When you save changes in a context, the changes are only committed “one store up.” If you save a child context, changes are pushed to its parent. Changes are not saved to the persistent store until the root context is saved. (A root managed object context is one whose parent context is nil.)
An object that meets the criteria specified by request (it is an instance of the entity specified by the request, and it matches the request’s predicate if there is one) and that has been inserted into a context but which is not yet saved to a persistent store, is retrieved if the fetch request is executed on that context.
Changes are not reflected in the context”. So what you’re seeing is normal. Batch updates work directly on the persistent store file instead of going through the managed object context, so the context doesn’t know about them. When you delete the objects by fetching and then deleting, you’re working through the context, so it knows about the changes you’re making (in fact it’s performing those changes for you).
mainContext would fetch all the way to the persistent store. Fetches and objectWithID: only go as many levels as they need to. It’s important to remember that when a context is created it’s a “snapshot” of the state of it’s parent. Subsequent changes to the parent will not be visible in the child unless the child is somehow invalidated
RootContext (private queue) - saves to persistent store MainContext (main queue) child of RootContext - use for UI (FRC) WorkerContext (private queue) - child of MainContext - use for updates & inserts
With Xcode 12.4, macOS 11.0 app. Every time we switch the system dark and light mode, the CPU goes up to 100%. Instruments show that there’s an increasing number of ButtonBehavior
Notice that sparkle:version="2.0" is CFBundleVersion which is your build number. You need to also specify sparkle:shortVersionString which is CFBundleShortVersionString your version number
<?xml version="1.0" encoding="utf-8"?> <rssversion="2.0"xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>Sparkle Test App Changelog</title> <link>http://sparkle-project.org/files/sparkletestcast.xml</link> <description>Most recent changes with links to updates.</description> <language>en</language> <item> <title>Version 2.0</title> <description> <![CDATA[ <ul> <li>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</li> <li>Suspendisse sed felis ac ante ultrices rhoncus. Etiam quis elit vel nibh placerat facilisis in id leo.</li> <li>Vestibulum nec tortor odio, nec malesuada libero. Cras vel convallis nunc.</li> <li>Suspendisse tristique massa eget velit consequat tincidunt. Praesent sodales hendrerit pretium.</li> </ul> ]]> </description> <pubDate>Sat, 26 Jul 2014 15:20:11 +0000</pubDate> <enclosureurl="https://sparkle-project.org/files/Sparkle%20Test%20App.zip"sparkle:version="2.0"length="107758"type="application/octet-stream"sparkle:dsaSignature="MCwCFCdoW13VBGJWIfIklKxQVyetgxE7AhQTVuY9uQT0KOV1UEk21epBsGZMPg==" /> </item> </channel> </rss>
finalclassAppDelegate: NSObject, NSApplicationDelegate{ funcapplicationDidFinishLaunching(_ notification: Notification) { let bundleId = Bundle.main.bundleIdentifier! // TODO: Make this more strict by only replacing at the end let mainBundleId = bundleId.replacingOccurrences(of: "-LaunchAtLoginHelper", with: "")
// Ensure the app is not already running guardNSRunningApplication.runningApplications(withBundleIdentifier: mainBundleId).isEmpty else { NSApp.terminate(nil) return }
let pathComponents = (Bundle.main.bundlePath asNSString).pathComponents let mainPath = NSString.path(withComponents: Array(pathComponents[0...(pathComponents.count - 5)])) NSWorkspace.shared.launchApplication(mainPath) NSApp.terminate(nil) } }
Error: code -1011 (Failed to authenticate for session: ( “Error Domain=ITunesConnectionAuthenticationErrorDomain Code=-22938 "Sign in with the app-specific password you generated. If you forgot the app-specific password or need to create a new one, go to appleid.apple.com" UserInfo={NSLocalizedRecoverySuggestion=Sign in with the app-specific password you generated. If you forgot the app-specific password or need to create a new one, go to appleid.apple.com, NSLocalizedDescription=Sign in with the app-specific password you generated. If you forgot the app-specific password or need to create a new one, go to appleid.apple.com, NSLocalizedFailureReason=App Store operation failed.}” ) Unable to upload your app for notarization.)
It succeeds, you will get ticket ID
1
Downloaded ticket has been stored at file:///var/folders/mn/whdh16qj6jldpcdpgyjhp35m0000gn/T/51e3b8bb-1b86-4cd6-abcd-bca469561234.ticket.
Recently when distributing staging releases of my app PastePal via GitHub release or Google Drive, people had hard time opening it
The displayed error is
You do not have permission to open the application
The more verbose error when using open command in Terminal is
The application cannot be opened for an unexpected reason, error=Error Domain=NSOSStatusErrorDomain Code=-10826 “kLSNoLaunchPermissionErr: User doesn’t have permission to launch the app (managed networks)” UserInfo={_LSFunction=_LSLaunchWithRunningboard, _LSLine=2508, NSUnderlyingError=0x7fa9c750d850 {Error Domain=RBSRequestErrorDomain Code=5 “Launch failed.” UserInfo={NSLocalizedFailureReason=Launch failed., NSUnderlyingError=0x7fa9c750e010 {Error Domain=NSPOSIXErrorDomain Code=153 “Unknown error: 153” UserInfo={NSLocalizedDescription=Launchd job spawn failed with error: 153}}}}}
From further investigation. This is restriction of Big Sur for downloaded zip file from browser
When decompressing the .zip, the application contents didn’t retain the execute bits. Add it back with sudo chmod -R 755 /path/to/app
Since the application was downloaded by a web browser the quarantine bits are set on the decompressed files. Remove that with sudo xattr -dr com.apple.quarantine /path/to/app
It still didn’t work despite the fact that I enabled apps from identified developers and that I also notarized my app.
The reason was I zip my PastePal.app before uploading to Google Drive. Now I just upload the bare PastePal.app, upon downloading Google Drive still zip the app but when I apply the chmod and xattr it works now 🎉
There’s Menu button it shows a dropdown style. We fake it by fading this and overlay with a button. allowsHitTesting does not work, but disabled seems to do the trick
NSTextView has this handy method to make scrollable NSTextView NSTextView.scrollableTextView(). The solution is to get to the responder outside enclosing NSScrollView, in my case it is the SwiftUI hosting view
1 2 3 4 5 6 7 8 9
classDisabledScrollTextView: NSTextView{ overridefuncscrollWheel(with event: NSEvent) { // 1st nextResponder is NSClipView // 2nd nextResponder is NSScrollView // 3rd nextResponder is NSResponder SwiftUIPlatformViewHost self.nextResponder?.nextResponder?.nextResponder?.scrollWheel(with: event) } }
Then we can construct with our new DisabledScrollTextView.scrollableTextView
NSStatusItem is backed by NSButton, we can animate this inner button. We need to specify position and anchorPoint for button’s layer so it rotates around its center point
1 2 3 4 5 6 7 8 9 10 11 12
guard let button = statusItem.button else { return }
From SwiftUI 2 for macOS 11.0, we have access to Menu for creating menu and submenu. Usually we use Button for interactive menu items and Text for disabled menu items.
The easy way to customize menu with image is to call Menu with content and label. Pay attention to how we use Button and Label inside Content to create interactive menu items
We can also use Image and Text separately. By default SwiftUI wraps these inside HStack automatically for us. For now, color has no effect in Menu, but it works on Text
Another thing is List, where we have selected and alternative background colors. We should also use dynamic system colors selectedContentBackgroundColor and alternatingContentBackgroundColors
Available when Data conforms to RandomAccessCollection, ID is Data.Element.ID, Content conforms to View, and Data.Element conforms to Identifiable.
So in our case, we use rawValue as id for Identifiable, so there’s mismatch between our selection being enum case and items in ForEach, which uses rawValue to uniquely identifies items. So our fix is to explicitly state that we want to use the enum case itself \.self as idfor ForEach
What we can also do is to declare enum case itself as id
1 2 3 4 5 6 7
enumPosition: String, Codable, CaseIterable, Identifiable{ var id: Position { self } caseleft caseright case bottom case top }
The lesson learned here is we need to ensure the underlying type of selection in List and id used in ForEach are the same
Starting from macOS 11, we can use List with SidebarListStyle inside NavigationView to declare master detail view. The SidebarListStyle makes list translucent. It automatically handles selection and marks selected row in list with accent color.
The trick is to set the button oinside of statusItem to send actions on both leftMouseUp and rightMouseUp.
Another thing to note is we use popUpMenu on NSStatusItem, although it is marked as deprecated on macOS 10.14. We can set menu but that overrides left click.
From iOS 7, every app downloaded from the store has a receipt (for downloading/buying the app) at appStoreReceiptURL. When users purchases something via In App Purchase, the content at appStoreReceiptURL is updated with purchases information. Most of the cases, you just need to refresh the receipt (at appStoreReceiptURL) so that you know which transactions users have made.
Note
Receipt is generated and bundled with your app when user download the app, whether it is free or paid
When user makes IAP, receipt is updated with IAP information
When user downloads an app (download free, or purchase paid app), they get future updates (whether free or paid) forever.
Call SKReceiptRefreshRequest or SKPaymentQueue.restoreCompletedTransactions asks for Appstore credential
When we build the app from Xcode or download from Testflight, receipt is not bundled within the app since the app is not downloaded from AppStore. We can use SKReceiptRefreshRequest to download receipt from sandbox Appstore
restoreCompletedTransactions updates app receipt
Receipt is stored locally on device, so when user uninstalls and reinstalls your app, there’s no in app purchases information, this is when you should refresh receipt or restoreCompletedTransactions
Users restore transactions to maintain access to content they’ve already purchased. For example, when they upgrade to a new phone, they don’t lose all of the items they purchased on the old phone. Include some mechanism in your app to let the user restore their purchases, such as a Restore Purchases button. Restoring purchases prompts for the user’s App Store credentials, which interrupts the flow of your app: because of this, don’t automatically restore purchases, especially not every time your app is launched.
In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt. The refreshed receipt contains a record of the user’s purchases in this app, on this device or any other device. However, some apps need to take an alternate approach for one of the following reasons:
If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content. If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
Refreshing the receipt asks the App Store for the latest copy of the receipt. Refreshing a receipt does not create any new transactions.
Restoring completed transactions creates a new transaction for every completed transaction the user made, essentially replaying history for your transaction queue observer.
Users sometimes need to restore purchased content, such as when they upgrade to a new phone.
Don’t automatically restore purchases, especially when your app is launched. Restoring purchases prompts for the user’s App Store credentials, which interrupts the flow of your app
In most cases, you only need to refresh the app receipt and deliver the products listed on the receipt. The refreshed receipt contains a record of the user’s purchases in this app, from any device the user’s App Store account is logged into
Refreshing a receipt doesn’t create new transactions; it requests the latest copy of the receipt from the App Store
Restoring completed transactions creates a new transaction for every transaction previously completed, essentially replaying history for your transaction queue observer. Your app maintains its own state to keep track of why it’s restoring completed transactions and how to handle them.
What are the different IAP types
From AppStore
Consumable (pay everytime)
A consumable In-App Purchase must be purchased every time the user downloads it. One-time services, such as fish food in a fishing app, are usually implemented as consumables.
Non-Consumable (one time payment)
Non-consumable In-App Purchases only need to be purchased once by users. Services that do not expire or decrease with use are usually implemented as non-consumables, such as new race tracks for a game app.
Auto-Renewable Subscriptions (will deduct money from your credit card on a cycle complete)
Auto-renewable Subscriptions allow the user to purchase updating and dynamic content for a set duration of time. Subscriptions renew automatically unless the user opts out, such as magazine subscriptions.
Free Subscription (no payment and is still visible even you did not submitted your account detail to itunes connect)
Free subscriptions are a way for developers to put free subscription content in Newsstand. Once a user signs up for a free subscription, it will be available on all devices associated with the user’s Apple ID. Note that free subscriptions do not expire and can only be offered in Newsstand-enabled apps.
Non-Renewing (need to renew manually)
Subscription Non-Renewing Subscriptions allow the sale of services with a limited duration. Non-Renewing Subscriptions must be used for In-App Purchases that offer time-based access to static content. Examples include a one week subscription to voice guidance feature within a navigation app or an annual subscription to online catalog of archived video or audio.
The receipt consists of a single file in the app bundle. The file is in a format called PKCS #7. The payload consists of a set of receipt attributes in a cross-platform format called ASN.1
1 2 3 4 5 6 7 8 9 10
case12: // Receipt Creation Date var dateStartPtr = ptr receiptCreationDate = readASN1Date(ptr: &dateStartPtr, maxLength: length)
case17: // IAP Receipt print("IAP Receipt.") case19: // Original App Version var stringStartPtr = ptr originalAppVersion = readASN1String(ptr: &stringStartPtr, maxLength: length)
Original Application Version The version of the app that was originally purchased. ASN.1 Field Type 19 ASN.1 Field Value UTF8STRING
Note
This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in macOS) in the Info.plist file when the purchase was originally made
CFBundleVersion is build number, and CFBundleShortVersionString is app version
1 2 3
In-App Purchase Receipt The receipt for an in-app purchase. ASN.1 Field Type 17
Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you do not have to switch between URLs while your application is tested, reviewed by App Review, or live in the App Store.
Show me the code
Let’s use enum to represent possible states for each resource. Here’s simple case where we only have 1 non consumable IAP product.
enumIAPError: Error{ case failedToRefreshReceipt case failedToRequestProduct case failedToPurchase case receiptNotFound }
enumIAPResourceState<T> { case notAsked case loading case success(T) case failure(IAPError) }
finalclassPricingPlan: ObservableObject{ staticlet pro = (Bundle.main.bundleIdentifier ?? "") + ".pro"
@Published var isPro: Bool = false @Published var product: IAPResourceState<SKProduct> = .notAsked @Published var purchase: IAPResourceState<SKPayment> = .notAsked @Published var receipt: IAPResourceState<InAppReceipt> = .notAsked }
Let’s have a central place for managing all IAP operations, called IAPManager, it can update our ObservableObjectPricingPlan hence triggers update to SwiftUI.
funcrequestProducts() { let identifiers = PricingPlan.pro let request = SKProductsRequest(productIdentifiers: Set(arrayLiteral: identifiers)) request.delegate = self pricingPlan.product = .loading request.start() }
funcpurchase(product: SKProduct) { guardSKPaymentQueue.canMakePayments() else { showAlert(text: "You are not allowed to make payment. Please check device settings.") return }
pricingPlan.purchase = .loading let payment = SKPayment(product: product) paymentQueue.add(payment) }
You can use restoreCompletedTransactions if you simply finishTransaction and grant user pro feature, like in this simple tutorial In-App Purchase Tutorial: Getting Started, search for SKPaymentTransactionObserver. restoreCompletedTransactions also updates receipt.
Otherwise refreshing receipt is a better idea. It serves both case when receipt is not there locally and when you want to restore transactions. With receipt refreshing, no restored transactions are created and SKPaymentTransactionObserver is not called, so we need to check receipt proactively.
Either restoreCompletedTransactions or SKReceiptRefreshRequest asks for AppStore credential so you should present a button there and ask user.
Check local receipt
Try to locate local receipt and examine it.
If it is not there (missing, corrupted), refresh receipt
If it’s there, check if it was from a version when the app was still as paid. Notice the difference in meaning of originalAppVersion in macOS and iOS
If it is not paid, check if this receipt contains In App Purchase information for our product
In practice, we need to perform some basic checks on receipt, like bundle id, app version, device id. Read In-App Purchases: Receipt Validation Tutorial, search for Validating the Receipt. TPInAppReceipt also has some handy verify functions
Besides verifying receipt locally, it is advisable to call verifyreceipt either on device, or better on serve to let Apple verify receipt and returns you a human readable json for receipt information.