Although we were able to do this Android, we haven't yet got the issuer name on iOS.
// after Microblink has looked at an image (from the camera roll)
func recognizerRunner(_ recognizerRunner: MBCRecognizerRunner, didFinishScanningWith state: MBCRecognizerResultState) {
if state == .valid { // values: .empty, .uncertain, .valid, .stageValid
let result = blinkCardRecognizer.result
DispatchQueue.main.async(execute: {() -> Void in
let details = VerificationDetails(firstSix: self.firstSix(result.cardNumber),
lastFour: self.lastFour(result.cardNumber),
name: result.owner,
brand: result.issuer)
self.delegate?.scanningSuccess(details: details)
})
}
else {
self.delegate?.scanningFailed()
}
}
The problem is that result.issuer is returning a MBCIssuer object, which I can't find any documentation for. How do I retrieve a string from a MBCIssuer?
Found it. Available from:
MBCBlinkCardUtils.issuer(toString: result.issuer)
Related
Background Info
I have a main application, which writes a single entry to a database contained in an App Group (we'll call them "DB1", and "Group1", respectively). To the same project, I have added an iOS 14 Home Screen Widget extension. This extension is then added to Group1.
All three sizes (small, medium, large) show the same information from the DB1 entry, just rearranged, and for the smaller widgets, some parts omitted.
Problem
The problem is, if I have multiple instances of the widget (say a small and a medium), then when I rebuild the target, and it loads/runs, I get CoreData errors such as the following:
<NSPersistentStoreCoordinator: 0x---MEM--->: Attempting recovery from error encountered during addPersistentStore: Error Domain=NSCocoaErrorDomain Code=134081 "(null)" UserInfo={NSUnderlyingException=Can't add the same store twice}
Relevant Code
Here's the code for the Timeline function
public func timeline(with context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
//Set up widget to refresh every minute
let currentDate = Date()
let refreshDate = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate)!
WidgetDataSource.shared.loadTimelineEntry { entry in
guard let e = entry else { return }
let entries: [TimelineEntry] = [e]
let timeline = Timeline(entries: entries, policy: .after(refreshDate))
completion(timeline)
}
}
And here is the loadTimelineEntry function
(persistentContainer gets initialized in the init() of WidgetDataSource, the class that holds loadTimelineEntry function)
func loadTimelineEntry(callback: #escaping (TimelineEntry?) -> Void) {
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
print("Loading persistent stores")
var widgetData: [WidgetData]
if let error = error {
print("Unresolved error \(error)")
callback(nil)
} else {
let request = WidgetData.createFetchRequest()
do {
widgetData = try self.persistentContainer.viewContext.fetch(request)
guard let data = widgetData.first else { callback(nil); return }
print("Got \(widgetData.count) WidgetData records")
let entry = TimelineEntry(date: Date())
callback(entry)
} catch {
print("Fetch failed")
callback(nil)
}
}
})
}
What I've Tried
Honestly, not much. I had gotten this same error before I put the widget into the Group1. That time it was due to the main app already having DB1 created on it's first run, then on the subsequent widget run, it looked in it's own container, didn't find it, and attempted to create it's own DB1, and the OS didn't let it due to them having the same name.
Widgets being a fairly new feature, there are not many questions regarding this specific use case, but I'm sure someone out there has a similar setup. Any help or tips will be highly appreciated!
Thanks to Tom Harrington's comment, I was able to solve the warnings by adding a check at the top of my loadTimelineEntry function like so:
if !persistentContainer.persistentStoreCoordinator.persistentStores.isEmpty {
//Proceed with fetch requests, etc. w/o loading stores
} else {
//Original logic to load store, and fetch data
}
I am trying to implement Watson Conversation API in my iOS app. While passing response.intents and response.entitities to to issueCommand function, I get an error "Cannot convert value of type '[RuntimeIntent]' to expected argument type '[Intent]'". I tried to typecast both the arguments of issueCommand arguments but it wasn't useful. It will be great if someone can guide me in the right direction? Thanks!
The code is as follows:
func conversationRequestResponse(_ text: String) {
let failure = { (error: Error) in print(error) }
let request = MessageRequest(input: InputData.init(text: text), context: self.context)
self.conversation?.message(workspaceID: Credentials.ConversationWorkspaceID,
request: request,
failure: failure) {
response in
print(response.output.text)
self.didReceiveConversationResponse(response.output.text)
self.context = response.context
var entities: ConversationV1.RuntimeEntity
/// An array of name-confidence pairs for the user input. Include the intents from the previous response when they do not need to change and to prevent Watson from trying to identify them.
// issue command based on intents and entities
print("appl_action: \(response.context.json["appl_action"])")
self.issueCommand(intents: response.intents, entities: response.entities)
}
}
func issueCommand(intents: [Intent], entities: [Entity]) {
for intent in intents {
print("intent: \(intent.intent), confidence: \(intent.confidence) ")
}
for entity in entities {
print("entity: \(entity.entity), value: \(entity.value)")
}
for intent in intents {
if intent.confidence > 0.9 {
switch intent.intent {
case "OnLight":
let command = Command(action: "On", object: "Light", intent: intent.intent)
sendToDevice(command, subtopic: "light")
case "OffLight":
let command = Command(action: "Off", object: "Light", intent: intent.intent)
sendToDevice(command, subtopic: "light")
case "TakePicture":
let command = Command(action: "Take", object: "Picture", intent: intent.intent)
sendToDevice(command, subtopic: "camera")
default:
print("No such command")
return
}
}
}
}
The errors can be seen in the image below:
enter image description here
Why not just change the signature of issueCommand to take RuntimeIntents and RuntimeEntities, like so:
func issueCommand(intents: [RuntimeIntent], entities: [RuntimeEntity]) {
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'm trying to get the users first name using cloud kit however the following code is not getting the users first name and is leaving firstNameFromFunction variable empty. Does anyone know how to achieve this in iOS 10?
let container = CKContainer.default()
container.fetchUserRecordID { (recordId, error) in
if error != nil {
print("Handle error)")
}else{
self.container.discoverUserInfo(
withUserRecordID: recordId!, completionHandler: { (userInfo, error) in
if error != nil {
print("Handle error")
}else{
if let userInfo = userInfo {
print("givenName = \(userInfo.displayContact?.givenName)")
print("familyName = \(userInfo.displayContact?.familyName)")
firstNameFromFunction = userInfo.displayContact?.givenName
}else{
print("no user info")
}
}
})
}
}
the permission screen that comes up when asking for the first time, IMO, is very poorly worded. They need to change that. It says "Allow people using 'your app' to look you up by email? People who know your email address will be able to see that you use this app." This make NO sense. This has nothing to do with asking the user to get their iCloud first name, last name, email address.
Speaking of email address - this and the phone number from the lookupInfo property is missing - i.e. set to nil, even though those values are legit and correct. Filing a bug tonight.
First, you will need to request permission to access the user's information.
Then, you can use a CKDiscoverUserIdentitiesOperation. This is just like any other CKOperation (eg. the modify record operation). You just need to create a new operation with the useridentitylookupinfo. Then you will also need to create a completion block to handle the results.
Here is an example function I created:
func getUserName(withRecordID recordID: CKRecordID,
completion: #escaping (String) -> ()) {
if #available(iOS 10.0, *) {
let userInfo = CKUserIdentityLookupInfo(userRecordID: recordID)
let discoverOperation = CKDiscoverUserIdentitiesOperation(userIdentityLookupInfos: [userInfo])
discoverOperation.userIdentityDiscoveredBlock = { (userIdentity, userIdentityLookupInfo) in
let userName = "\((userIdentity.nameComponents?.givenName ?? "")) \((userIdentity.nameComponents?.familyName ?? ""))"
completion(userName)
}
discoverOperation.completionBlock = {
completion("")
}
CKContainer.default().add(discoverOperation)
} else {
// iOS 10 and below version of the code above,
// no longer works. So, we just return an empty string.
completion("")
}
}
First you need to ask the user for permission to be discovered.
Use CKContainer.default().requestApplicationPermission method passing .userDiscoverability on applicationPermission parameter.
The CKContainer.default().discoverUserInfo method is deprecated on iOS 10. Instead use CKContainer.default().discoverUserIdentity method.
Do something like:
CKContainer.default().requestApplicationPermission(.userDiscoverability) { (status, error) in
CKContainer.default().fetchUserRecordID { (record, error) in
CKContainer.default().discoverUserIdentity(withUserRecordID: record!, completionHandler: { (userIdentity, error) in
print("\(userIdentity?.nameComponents?.givenName)")
print("\(userIdentity?.nameComponents?.familyName)")
})
}
}
It seems that there is a dictionary value I can place in the GET call that corresponds to a property on PDKPin that I can call in my success block to unpack the value, i.e. "url" = PDKPin.url "note" = PDKPin.decriptionText, however I can't find a corresponding property for "link" which is available per the documentation https://developers.pinterest.com/docs/api/pins/?
PDKClient.sharedInstance().getAuthenticatedUserPinsWithFields(Set(["url", "note", "link"]), success:
{ (responseObject :PDKResponseObject!) -> Void in
print("success /(pdk)")
let currentResponseObject = responseObject
let pins = currentResponseObject.pins
self.pinArray = pins()
for pin in self.pinArray {
if let pinAsPDKPin = pin as? PDKPin {
print(pinAsPDKPin.descriptionText)
if let safeURL = pinAsPDKPin.url {
print(safeURL)
}
let link = pinAsPDKPin.link //no such property on PDKPin
}
}
}) { (err :NSError!) -> Void in
print("error NSError: \(err)")
}
It looks kinda weird, but as per the implementation, the 'link' property(from the API) is used as 'url'(in the SDK). Check out the code here : https://github.com/pinterest/ios-pdk/blob/master/Pod/Classes/PDKPin.m#L38
So, if all that you care about is the 'link' from the API, then just access 'url' in the SDK(in PDKPin object).