Many the tutorials around StoreKit 2 and even Apple's own Sample Code reference "StoreKit testing in Xcode so you can build and run the sample app without completing any setup in App Store Connect. The project defines in-app products for the StoreKit testing server in the Products.storekit file." I have an app with auto renewing subscriptions already set up in App Store Connect (migrating to StoreKit 2 from SwiftyStoreKit)...how can I set up this StoreKitManager class to check for an active subscription without needing to make a separate Products.plist file? This code below, largely based on Apple's sample code, results in Error Domain=ASDErrorDomain Code=509 "No active account" which is obvious as I can't figure out how to connect my products to the StoreKit 2 logic? 🤔
EDIT: here is a gist of my code
import Foundation
import StoreKit
typealias Transaction = StoreKit.Transaction
public enum StoreError: Error {
case failedVerification
}
#available(watchOSApplicationExtension 8.0, *)
class WatchStoreManager: ObservableObject {
var updateListenerTask: Task<Void, Error>? = nil
init() {
print("Init called in WatchStoreManager")
//Start a transaction listener as close to app launch as possible so you don't miss any transactions.
updateListenerTask = listenForTransactions()
}
deinit {
updateListenerTask?.cancel()
}
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
//Iterate through any transactions that don't come from a direct call to `purchase()`.
for await result in Transaction.updates {
do {
let transaction = try self.checkVerified(result)
print("we have a verified transacction")
//Deliver products to the user.
//TODO:
//await self.updateCustomerProductStatus()
//Always finish a transaction.
await transaction.finish()
} catch {
//StoreKit has a transaction that fails verification. Don't deliver content to the user.
print("Transaction failed verification")
}
}
}
}
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
//Check whether the JWS passes StoreKit verification.
switch result {
case .unverified:
//StoreKit parses the JWS, but it fails verification.
throw StoreError.failedVerification
case .verified(let safe):
//The result is verified. Return the unwrapped value.
return safe
}
}
}
509: No active account
Means the user isn't signed in to the app store. Go to Settings -> Sign in to Your iPhone and sign in with a valid App Store or sandbox account
Edit: I see you are logged in on your device - that's strange. You mentioned you haven't linked your products. You need to fetch products from the App Store with something similar to the following. But I wouldn't expect you to see that specific error message...
enum AppProduct: String, CaseIterable, Identifiable {
case noAds = "MYUNIQUEIDENTIFIER_ESTABLISHEDIN_APPSTORECONNECT"
var id: String {
UUID().uuidString
} // to make it Identifiable for use in a List
static var allProductIds: [String] {
return Self.allCases.map { $0.rawValue }
} // convenience
}
#MainActor
#discardableResult func fetchProducts() async throws -> [Product] {
let products = try await Product.products(for: AppProduct.allProductIds) // this is the call that fetches products
guard products.first != nil else { throw PurchaseError.unknown } // custom error
self._storeProducts = products
let fetchedProducts: [AppProduct] = products.compactMap {
let appProduct = AppProduct(rawValue: $0.id)
return appProduct
}
self.fetchedProducts = fetchedProducts
try await checkPurchased()
return products
}
private func checkPurchased() async throws {
for product in _storeProducts {
guard let state = await product.currentEntitlement else { continue }
let transaction = try self.checkVerified(state)
//Always finish a transaction.
await transaction.finish()
}
}
I'm setting purchase state in checkVerified when verification passes...
I'm implementing a File Provider Extension for iOS 11.
Dispite watching the conference at https://developer.apple.com/videos/play/wwdc2017/243/ and navigating through Apple's Documentation, I still can't seem to understand how to implement some of the methods for NSFileProviderExtension and NSFileProviderEnumerator objects.
I successfully implemented NSFileProviderItem, having all of them listed in the Navite iOS 11 Files App. However, I can't trigger any document based app to open upon selecting a file.
I overrided all the methods for the NSFileProviderExtension. Some are still empty, but I placed a breakpoint to check whenever they are called.
The NSFileProviderExtension looks something like this:
class FileProviderExtension: NSFileProviderExtension {
var db : [FileProviderItem] = [] //Used "as" a database
...
override func item(for identifier: NSFileProviderItemIdentifier) throws -> NSFileProviderItem {
for i in db {
if i.itemIdentifier.rawValue == identifier.rawValue {
return i
}
}
throw NSError(domain: NSCocoaErrorDomain, code: NSNotFound, userInfo:[:])
}
override func urlForItem(withPersistentIdentifier identifier: NSFileProviderItemIdentifier) -> URL? {
guard let item = try? item(for: identifier) else {
return nil
}
// in this implementation, all paths are structured as <base storage directory>/<item identifier>/<item file name>
let manager = NSFileProviderManager.default
let perItemDirectory = manager.documentStorageURL.appendingPathComponent(identifier.rawValue, isDirectory: true)
return perItemDirectory.appendingPathComponent(item.filename, isDirectory:false)
}
// MARK: - Enumeration
func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
var maybeEnumerator: NSFileProviderEnumerator? = nil
if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
maybeEnumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
self.db = CustomData.getData(pid: containerItemIdentifier)
} else if (containerItemIdentifier == NSFileProviderItemIdentifier.workingSet) {
// TODO: instantiate an enumerator for the working set
} else {
}
guard let enumerator = maybeEnumerator else {
throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
}
return enumerator
}
My enumerateItems looks something like so:
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
override func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {
let itens = CustomData.getData(pid: enumeratedItemIdentifier)
observer.didEnumerate(itens)
observer.finishEnumerating(upTo: nil)
}
The static function CustomData.getData is used for testing. It returns an array of NSFileProviderItem with the desired properties. It should be replaced with a database, as explained in the conference.
class CustomData {
static func getData(pid : NSFileProviderItemIdentifier) -> [FileProviderItem] {
return [
FileProviderItem(uid: "0", pid: pid, name: "garden", remoteUrl : "https://img2.10bestmedia.com/Images/Photos/338373/GettyImages-516844708_54_990x660.jpg"),
FileProviderItem(uid: "1", pid: pid, name: "car", remoteUrl : "https://static.pexels.com/photos/170811/pexels-photo-170811.jpeg"),
FileProviderItem(uid: "2", pid: pid, name: "cat", remoteUrl : "http://www.petmd.com/sites/default/files/what-does-it-mean-when-cat-wags-tail.jpg"),
FileProviderItem(uid: "3", pid: pid, name: "computer", remoteUrl : "http://mrslamarche.com/wp-content/uploads/2016/08/dell-xps-laptop-620.jpg")
]
}
}
The problem is, when the user presses a document, urlForItem is successfully called but nothing happens upon returning the item url.
What am I doing wrong?
I can't find any examples on the internet.
Cheers
-nls
Turns out, I did not correctly implement providePlaceholder(at url:).
It is now solved.
Cheers
-nls
EDIT:
In order to list the items in your file provider, the method enumerator(for:) should be implemented.
This method will receive a containerItemIdentifier, as if telling you "what folder the user is trying to access". It returns a NSFileProviderEnumerator object, that should also be implemented by you.
Here is an example of how a simple enumerator(for:) method should look like:
class FileProviderExtension: NSFileProviderExtension {
override func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
var enumerator: NSFileProviderEnumerator? = nil
if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
enumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
}
else {
enumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
}
if enumerator == nill {
throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
}
return enumerator
}
(...)
}
Again, as I said, the FileProviderEnumerator should be implemented by you. The important method here is the enumerateItems(for observer:, startingAt page:)
Here it is how it should look:
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {
if (enumeratedItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
//Creating an example of a folder item
let folderItem = FileProviderFolder()
folderItem.parentItemIdentifier = enumeratedItemIdentifier //<-- Very important
folderItem.typeIdentifier = "public.folder"
folderItem.name = "ExampleFolder"
folderItem.id = "ExampleFolderID"
//Creating an example of a file item
let fileItem = FileProviderFile()
fileItem.parentItemIdentifier = enumeratedItemIdentifier //<-- Very important
fileItem.typeIdentifier = "public.plain-text"
fileItem.name = "ExampleFile.txt"
fileItem.id = "ExampleFileID"
self.itemList.append(contentsOf: [folderItem, fileItem])
observer.didEnumerate(self.itemList)
observer.finishEnumerating(upTo: nil)
}
else {
//1 > Find directory name using "enumeratedItemIdentifier" property
//2 > Fetch data from the desired directory
//3 > Create File or Folder Items
//4 > Send items back using didEnumerate and finishEnumerating
}
}
(...)
}
Remember that we were creating these FileProviderEnumerators, giving them the containerItemIdentifier. This property is used to determine what folder the user is trying to access.
Very important note: Each item, File or Folder, should have its parentItemIdentifier property defined. If this property is not set, the items won't appear when the user tries to open the parent folder.
Also, as the name suggests, typeIdentifier will hold the Uniform Type Identifier (UTI) for the item.
Finally, the last object we should implement is the NSFileProviderItem. Both File and Folder items are very similar, and should differ in their typeIdentifier property.
Here is a very simple example of a folder:
class FileProviderFolder: NSObject, NSFileProviderItem {
public var id: String?
public var name: String?
var parentItemIdentifier: NSFileProviderItemIdentifier
var typeIdentifier: String
init() {
}
var itemIdentifier: NSFileProviderItemIdentifier {
return NSFileProviderItemIdentifier(self.id!)
}
var filename: String {
return self.name!
}
}
The itemIdentifier is very important because, as stated before, this property will provide the directory name for the folder item when trying to enumerate its contents (refer to enumerator(for:) method).
EDIT2
If the user selects a file, the method startProvidingItem(at url:) should be called.
This method should perform 3 tasks:
1 - Find the selected item ID (usualy using the provided url, but you can use a database too)
2 - Download the file to the local device, making it available at the specified url. Alamofire does this;
3 - Call completionHandler;
Here is a simple example of this method:
class FileProviderExtension: NSFileProviderExtension {
override func urlForItem(withPersistentIdentifier identifier: NSFileProviderItemIdentifier) -> URL? {
// resolve the given identifier to a file on disk
guard let item = try? item(for: identifier) else {
return nil
}
// in this implementation, all paths are structured as <base storage directory>/<item identifier>/<item file name>
let perItemDirectory = NSFileProviderManager.default.documentStorageURL.appendingPathComponent(identifier.rawValue, isDirectory: true)
let allDir = perItemDirectory.appendingPathComponent(item.filename, isDirectory:false)
return allDir
}
override func persistentIdentifierForItem(at url: URL) -> NSFileProviderItemIdentifier? {
// exploit that the path structure has been defined as <base storage directory>/<item identifier>/<item file name>, at urlForItem
let pathComponents = url.pathComponents
assert(pathComponents.count > 2)
return NSFileProviderItemIdentifier(pathComponents[pathComponents.count - 2])
}
override func startProvidingItem(at url: URL, completionHandler: #escaping (Error?) -> Void) {
guard
let itemID = persistentIdentifierForItem(at: url),
let item = try? self.item(for: itemID) as! FileProviderFile else {
return
}
DownloadfileAsync(
file: item,
toLocalDirectory: url,
success: { (response) in
// Do necessary processing on the FileProviderFile object
// Example: setting isOffline flag to True
completionHandler(nil)
},
fail: { (response) in
completionHandler(NSFileProviderError(.serverUnreachable))
}
)
}
(...)
}
Note that, to get the ID from the URL, I'm using the recomended method: the URL it self contains the item ID.
This URL is definedin the urlForItem method.
Hope this helps.
-nls
I thought I'd provide a followup answer, the primary answer is great as a first step. In my case startProvidingItem was not called because I was not storing the files in exactly the directory the system was looking for, that is to say:
<Your container path>/File Provider Storage/<itemIdentifier>/My Awesome Image.png
That is on the slide from WWDC17 on the FileProvider extension, but I did not think it must follow that format so exactly.
I had a directory not named "File Provider Storage" into which I was putting files directly, and startProvidingItem was never called. It was only when I made a directory for the uniqueFileID into which the file was placed, AND renamed my overall storage directory to "File Provider Storage" that startProvidingItem was called.
Also note that with iOS11, you'll need to provide a providePlaceholder call as well to the FileProviderExtension, use EXACTLY the code that is in the docs for that and do not deviate unless you are sure of what you are doing.
I am creating a swift 3 parse live query messaging application. When a picture is sent, if the subscription gets the new message then i get the following error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI getDataInBackgroundWithBlock:]: unrecognized selector sent to instance 0x60000106e9c0'
When I reload the page and the original query gets the picture it works fine.
Here is my code
this is my original query that I subscribe to
var messagesQuery: PFQuery<Message> {
return (Message.query()?
.whereKey("roomName", equalTo: chatName)
.order(byAscending: "createdAt")) as! PFQuery<Message>
Here is my subscription to that query
func subscribeToUpdates() {
subscription = liveQueryClient
.subscribe(messagesQuery)
.handle(Event.created) { _, message in
self.messages.append(message)
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
})
}
}
Here is the code causing the crash to occur (again only when the message is recieved via live query subscription rather than traditional query)
(message.image)?.getDataInBackground(block: { (data:Data?, error:Error?) in
if error == nil {
cell.messageImageView.image = UIImage(data: data!)
}
})
I have the following:
let processURLS = processingViewController()
Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(processURLS.getURLsToSend),
userInfo: nil,
repeats: true)
When this event is triggered, I received the following error and I'm not sure why this doesn't work
2016-11-09 14:47:00.504932 AcumenJLR[3414:905978] * Terminating app
due to uncaught exception 'NSInvalidArgumentException', reason:
'-[AcumenJLR.homeViewController getURLsToSend]: unrecognized selector
sent to instance 0x100d25c60'
* First throw call stack: (0x1816721c0 0x1800ac55c 0x181679278 0x181676278 0x18157059c 0x18215c8f8 0x1816208f4 0x181620608
0x18161fec4 0x18161dac0 0x18154c048 0x182fd2198 0x1875372fc
0x187532034 0x100114620 0x1805305b8) libc++abi.dylib: terminating with
uncaught exception of type NSException
Here is the getURLsToSend method
func getURLsToSend () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<URLsToSend> = URLsToSend.fetchRequest()
let context = getContext()
do {
//Get results
let searchResults = try getContext().fetch(fetchRequest)
print ("num of results = \(searchResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for urls in searchResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
//print("\(urls.value(forKey: "url"))")
let currentURL = urls.value(forKey: "url")!
//print(urls.value(forKey: "url")!)
completeLoadAction(urlString: currentURL as! String) { code in
if (code == 200){
context.delete(urls)
}
}
}
} catch {
print("Error with request: \(error)")
}
According to the target / action pattern the method specified in selector must be declared in the class specified in target in this case the current class self.
Either change the target or implement the method in self.
May be you have some paramter in method but you are not sending any value from there
I am trying to use Microsoft Graph iOS SDK and followed the official code sample.
The following code snippet is to get basic profile of all users in the organization:
private func getContactInfo(){
self.graphClient.users().request().getWithCompletion{
(collection: MSCollection?, request:MSGraphUsersCollectionRequest?, error: NSError?) in
if let users = collection {
for user: MSGraphUser in users.value as! [MSGraphUser] {
print(user)
print(String(user.surname.dynamicType))
print(user.mobilePhone)
}
}
}
}
Output Result:
{
businessPhones = (
);
displayName = "Boss";
givenName = "Jack";
id = "30fb78ff-522f-45e7-a9cd-75ba8ee2eca6";
jobTitle = "<null>";
mail = "boss#abc.net";
mobilePhone = "<null>";
officeLocation = "<null>";
preferredLanguage = "<null>";
surname = "\U8a79";
userPrincipalName = "boss#abc.net";
}
ImplicitlyUnwrappedOptional<String>
And runtime exception happened
2016-06-05 17:02:55.302 ABC[76976:915052] -[NSNull _fastCStringContents:]: unrecognized selector sent to instance 0xb59398
2016-06-05 17:02:55.305 ABC[76976:915052] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull _fastCStringContents:]: unrecognized selector sent to instance 0xb59398'
*** First throw call stack:
(
0 CoreFoundation 0x0092c494 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x02640e02 objc_exception_throw + 50
...
When I try to print the null value like mobilePhone, runtime exception occurred. How to detect null value to avoid crashing? Thank you.
Add: MSGraphUser.m snippet used in this case,
#interface MSGraphUser()
{
...
NSString* _mobilePhone;
...
- (NSString*) mobilePhone
{
return self.dictionary[#"mobilePhone"];
}
...
There must be a better swift support, but for now, you could extend the class like what Casey has said.
If you did this, you can check
if user.optMobilePhone is NSNull
The actual extension would look something like this:
extension MSGraphUser {
var optMobilePhone: AnyObject? {
return dictionaryFromItem()["mobilePhone"]
}
}
You can use one way to filter null objects
- (NSString* __nullable) mobilePhone
{
if ([self.dictionary isKindOfClass:[NSString class]])
{
return self.dictionary[#"mobilePhone"];
}
return nil;
}
or
- (NSString* __nullable) mobilePhone
{
if (![self.dictionary isKindOfClass:[NSNull class]]) {
return self.dictionary[#"mobilePhone"];
}
return nil;
}
Many JSON APIs will return null in the JSON document for values that they want to mark explicitely as not present. This is different from keys that will not be reported at all.
The first thing you do is check with the creator of the API or with the documentation how that kind of value should be interpreted. For example, if you ask for the key "user", you might find that the value isn't present at all, that the value is null, or that the value is an empty string. Find out how each value should be interpreted, or if they should be treated the same.
Then since you will need this all the time, you add a function to NSDictionary that will return what you want, and logs things that you didn't expect. You check that a value isn't there at all by checking with if let ... . You check whether a value is null by checking value == NSNull (). And then you make that function return either an optional string or a string.
It looks like mobilePhone can actually return a NSString or NSNull so it should be declared as - (id) mobilePhone. Since it's not, we can extend MSGraphUser
extension MSGraphUser {
var optMobilePhone : String? { return self.dictionary["mobilePhone"] as? String }
}
And then use that computed property to safely access the value:
for obj in users.value {
if let user = obj as? MSGraphUser {
print(user)
print(String(user.surname.dynamicType))
print(user.optMobilePhone ?? "no phone")
}
}