I am trying to create an iOS share extension to share contacts using the following code:
let contactType = "public.vcard"
override func viewDidLoad() {
let items = extensionContext?.inputItems
var itemProvider: NSItemProvider?
if items != nil && items!.isEmpty == false {
let item = items![0] as! NSExtensionItem
if let attachments = item.attachments {
if !attachments.isEmpty {
itemProvider = attachments[0] as? NSItemProvider
}
}
}
if itemProvider?.hasItemConformingToTypeIdentifier(contactType) == true {
itemProvider?.loadItemForTypeIdentifier(contactType, options: nil) { item, error in
if error == nil {
print("item: \(item)")
}
}
}
}
I am getting the following output:
item: Optional(<42454749 4e3a5643 4152440d 0a564552 53494f4e 3a332e30 0d0a5052 4f444944 3a2d2f2f 4170706c 6520496e 632e2f2f 6950686f 6e65204f 5320392e 322f2f45 4e0d0a4e 3a42656e 6e657474 3b436872 69733b3b 3b0d0a46 4e3a2043 68726973 20204265 6e6e6574 74200d0a 54454c3b 74797065 3d484f4d 453b7479 70653d56 4f494345 3b747970 653d7072 65663a28 36313529 c2a03432 352d3637 31390d0a 454e443a 56434152 440d0a>)
If i set a breakpoint I see that item has a type of NSSecureCoding. My question is how do I turn this into a CNContact?
The item you are getting is of type NSData. You have to transform it to String to get the vCard String:
if itemProvider?.hasItemConformingToTypeIdentifier(contactType) == true {
itemProvider?.loadItemForTypeIdentifier(contactType, options: nil) { item, error in
if error == nil {
if let data = item as? NSData, let vCardString = String(data: data, encoding: NSUTF8StringEncoding) {
print(vCardString)
}
}
}
}
When you are sharing any contacts, itemProvider contains vCard format of CNContact. To access that contact, you need to convert that data (type NSSecureCoding) to CNContact.
let uttypeContact = kUTTypeContact as String
if itemProvider.hasItemConformingToTypeIdentifier(uttypeContact) {
itemProvider.loadItemForTypeIdentifier(uttypeContact, options: nil, completionHandler: { (data, error) in
if error == nil {
do {
if let data = data as? NSData {
let contact = try CNContactVCardSerialization.contactsWithData(data)
print(contact)
}
} catch {}
}
})
}
[Update]
For more details https://forums.developer.apple.com/message/92115#92115
Updated answer of #joern to Swift 3:
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeContact as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeContact as String, options: nil, completionHandler: { (contact, error) in
if let contactData = contact as? Data, let vCardString = String(data: contactData, encoding: .utf8) {
print(vCardString)
}
})
}
Also for an Action Extension you need to add the OperationQueue:
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeContact as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeContact as String, options: nil, completionHandler: { (contact, error) in
OperationQueue.main.addOperation {
if let contactData = contact as? Data, let vCardString = String(data: contactData, encoding: .utf8) {
print(vCardString)
}
}
})
}
Related
So I'm trying to store data retrieved from my Firestore database into an object. My database has a collection of users, and each user has a collection of classes. I want to be able to get the logged in users collection of classes and store them in an array of objects. Most of what I've tried so far can pull data but it won't save it into anything because its able access the data from within the completion handler. Any help would be great, here's the code I'm working with rn:
db.collection("users").whereField("uid", isEqualTo: uid).addSnapshotListener { (querySnapshot, error) in
if error == nil && querySnapshot != nil {
let docId = querySnapshot?.documents[0].documentID
db.collection("users").document(docId!).collection("classes").addSnapshotListener { (querySnap, error) in
guard let documents = querySnap?.documents else{print("No Classes");return}
var imageData:UIImage?
retrievedClasses = documents.map { (querySnap) -> UserClass in
let data = querySnap.data()
if let decodedData = Data(base64Encoded: data["class_img"] as! String, options: .ignoreUnknownCharacters){
imageData = UIImage(data: decodedData)
}
return UserClass.init(name: data["class_name"] as! String, desc: data["class_desc"] as! String, img: imageData!, color: data["class_color"] as! String, link: data["class_link"] as! String, location: data["class_location"] as! GeoPoint, meetingTime: data["meeting_time"] as! Dictionary<String,String>)
}
print(retrievedClasses[0].printClass())
}
}
}
As I understand you need to do something like this:
func getUserClasses(for userID: String, completion: #escaping (Result<[UserClass], Error>) -> Void) {
db.collection("users").whereField("uid", isEqualTo: userID).addSnapshotListener { (querySnapshot, error) in
if error == nil && querySnapshot != nil {
let docId = querySnapshot?.documents[0].documentID
db.collection("users").document(docId!).collection("classes").addSnapshotListener { (querySnap, error) in
guard let documents = querySnap?.documents else {
print("No Classes")
completion(.failure("No Classes"))
return
}
let retrievedClasses = documents.map { (querySnap) -> UserClass in
let data = querySnap.data()
var imageData: UIImage?
if let decodedData = Data(base64Encoded: data["class_img"] as! String,
options: .ignoreUnknownCharacters) {
imageData = UIImage(data: decodedData)
}
let user = UserClass(name: data["class_name"] as! String,
desc: data["class_desc"] as! String,
img: imageData!,
color: data["class_color"] as! String,
link: data["class_link"] as! String,
location: data["class_location"] as! GeoPoint,
meetingTime: data["meeting_time"] as! Dictionary<String,String>)
return user
}
completion(.success(retrievedClasses))
}
} else {
completion(.failure("Request Error"))
}
}
}
Then you will able to use data after request completion:
getUserClasses(for: "123") { result in
switch result {
case .success(let allClasses):
retrievedClasses = allClasses // retrievedClasses is a property in your class which you are going to use
if !retrievedClasses.isEmpty() {
print(retrievedClasses[0].printClass())
}
case .failure(let error):
print(error)
}
}
I have a ShareExtension in which I like need to get the current URL. This is my function for it:
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
}
}
}
My problem is that I need the html but when using that variable right after that function html is still empty. I think I need some sort of completion handler but I tried different things now and can not get it right...
This is how my whole function looks like at the moment (not working, as html becomes an empty String)
#objc func actionButtonTapped(){
do {
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
}
}
}
let doc: Document = try SwiftSoup.parse(html ?? "")
let priceClasses: Elements = try doc.select("[class~=(?i)price]")
for priceClass: Element in priceClasses.array() {
let priceText : String = try priceClass.text()
print(try priceClass.className())
print("pricetext: \(priceText)")
}
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
for imageName in srcsStringArray {
print(imageName!)
}
} catch Exception.Error( _, let message) {
print(message)
} catch {
print("error")
}
}
The Goal is to have a extra function to get the url (1st code example) with a completion handler in which I can work with the created html.
the problem was that I didn't realize that I already had a completionHandler with loadItems. So what I did now was to put the whole do & catch block in another method and called it in the completion handler like this:
#objc func actionButtonTapped(){
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
print("bruh")
self.doStuff(html: html)
}
}
}
}
func doStuff(html: String?){
do {
let doc: Document = try SwiftSoup.parse(html ?? "")
let priceClasses: Elements? = try doc.select("[class~=(?i)price]")
for priceClass: Element in priceClasses!.array() {
let priceText : String = try priceClass.text()
print(try priceClass.className())
print("pricetext: \(priceText)")
}
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
for imageName in srcsStringArray {
print(imageName!)
}
} catch Exception.Error( _, let message) {
print(message)
} catch {
print("error")
}
}
I have data coming from Json and populating it inside an UIImage using alamofireImage. One of those elements that I am getting back from Json is a String that has a URL to an image. If a particular Json string is null or blank I get the following error :
fatal error: Index out of range
EventEngine.getEventGalery(invitedID, limit: "2") { (result, error) in
DispatchQueue.main.async(execute: { () -> Void in
if let response = result as? EventGaleryResponse{
self.galery = response.data!
let jsonDict = self.galery
print("jsonDict \(jsonDict![0].photo!)")
if jsonDict![0].photo != nil {
self.imagearry1.af_setImage(withURL: URL(string: jsonDict![0].photo!)!)
}
if jsonDict![1].photo != nil {
self.imagearry2.af_setImage(withURL: URL(string:jsonDict![1].photo!)!)
}
if jsonDict![2].photo != nil {
self.imagearry3.af_setImage(withURL: URL(string: jsonDict![2].photo!)!)
}
}
})
}
Please never use ! operator without checks. I really suggest to use if let construction instead.
Some pseudocode (I dont know your types) :
EventEngine.getEventGalery(invitedID, limit: "2") { (result, error) in
DispatchQueue.main.async(execute: { () -> Void in
if let response = result as? EventGaleryResponse{
self.galery = response.data!
let jsonDict = self.galery
if let dict = jsonDict {
setPhotoFromDict(dict, 0, self.imagearry1)
setPhotoFromDict(dict, 1, self.imagearry2)
setPhotoFromDict(dict, 2, self.imagearry3)
} else {
print("cannot deserialise \(String(describing: jsonDict)")
}
}
})
}
private func setPhotoFromDict(<#DictType#> dict, Int index, <#ImageArrayType#> imageArary) {
if let photo = dict[index].photo as? String, let url = URL(string: photo) {
imageArary.af_setImage(withURL: url)
}
}
And the initial error comes from this line print("jsonDict \(jsonDict![0].photo!)"), I guess, because you access object without check
I think, you did not check if it is empty
EventEngine.getEventGalery(invitedID, limit: "2") { (result, error) in
DispatchQueue.main.async(execute: { () -> Void in
if let response = result as? EventGaleryResponse{
self.galery = response.data!
let jsonDict = self.galery
if jsonDict != nil {
if jsonDict.count > 0 && jsonDict![0].photo != nil {
self.imagearry1.af_setImage(withURL: URL(string: jsonDict![0].photo!)!)
}
if jsonDict.count > 1 && jsonDict![1].photo != nil {
self.imagearry2.af_setImage(withURL: URL(string:jsonDict![1].photo!)!)
}
if jsonDict.count > 2 && jsonDict![2].photo != nil {
self.imagearry3.af_setImage(withURL: URL(string: jsonDict![2].photo!)!)
}
}
}
})
}
I'm developing share extension which is used on safari.
I could get url on share extension. but I cant get page title.
let puclicURL = String(kUTTypeURL)
if itemProvider.hasItemConformingToTypeIdentifier(puclicURL) {
itemProvider.loadItem(forTypeIdentifier: puclicURL, options: nil, completionHandler: {
(item, error) in
if let url: NSURL = item as? NSURL {
print("url", url)
// I want page title also
}
}
}
And, I tried below code.https://stackoverflow.com/a/33139355/5060282
I think below code can run only in Action Extension. not Share Extension.
let propertyList = String(kUTTypePropertyList)
if itemProvider.hasItemConformingToTypeIdentifier(propertyList) {
itemProvider.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in
let dictionary = item as! NSDictionary
OperationQueue.main.addOperation {
let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary
let title = NSURL(string: (results["title"] as! String))
//yay, you got the title now
print(title)
}
})
} else {
print("error")
}
// But, error...
My problem arises when I want to populate data from my mysql database into a class object. I am trying to return an array of objects and it returns nil and then it fills itself somehow. How can I make it fill before returning the blank array?
Here is my code and a screenshot of code output
import Foundation
class Research
{
var mainResearchImageURL:String = ""
var userProfileImageURL:String = ""
var caption:String = ""
var shortDescription:String = ""
init(mainResearchImageURL :String, userProfileImageURL:String, caption:String, shortDescription:String)
{
self.mainResearchImageURL = mainResearchImageURL
self.userProfileImageURL = userProfileImageURL
self.caption = caption
self.shortDescription = shortDescription
}
class func downloadAllResearches()->[Research]
{
var researches = [Research]()
let urlString = "http://localhost/test/index.php"
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"
let postString = "action=listresearches"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
if (error == nil) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let dictionary = json!.firstObject as? NSDictionary
var counter:Int = 0;
for line in json!{
let researchData = line as! NSDictionary
let researchLineFromData = Research(mainResearchImageURL: researchData["research_mainImageURL"] as! String, userProfileImageURL: researchData["research_creatorProfileImageURL"] as! String, caption: researchData["research_caption"] as! String, shortDescription: researchData["research_shortDescription"] as! String)
researches.append(researchLineFromData) //researches bir dizi ve elemanları Research türünde bir sınıftan oluşuyor.
counter += 1
print ("counter value \(counter)")
print("array count in loop is = \(researches.count)")
}
}catch let error as NSError{
print(error)
}
} else {
print(error)
}})
task.resume()
print("array count in return is = \(researches.count)")
return researches
}
}
And this is the output:
add this on you completionHandler ( it works if you update a view)
dispatch_async(dispatch_get_main_queue(), {
if (error == nil) { ...... }
})
Advice 1:
return the task and use a completion param in your method,
you can cancel the task if it's too slow.
Advice 2 :
Use alamofire and swiftyJson framework
What happen here is that you are returning the value before finish (remember that the call is Asynchronous), you can make something like this:
class func downloadAllResearches(success:([Research])->Void,failure:(String)->Void)
{
var researches = [Research]()
let urlString = "http://localhost/test/index.php"
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"
let postString = "action=listresearches"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
if (error == nil) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let dictionary = json!.firstObject as? NSDictionary
var counter:Int = 0;
for line in json!{
let researchData = line as! NSDictionary
let researchLineFromData = Research(mainResearchImageURL: researchData["research_mainImageURL"] as! String, userProfileImageURL: researchData["research_creatorProfileImageURL"] as! String, caption: researchData["research_caption"] as! String, shortDescription: researchData["research_shortDescription"] as! String)
researches.append(researchLineFromData) //researches bir dizi ve elemanları Research türünde bir sınıftan oluşuyor.
counter += 1
print ("counter value \(counter)")
print("array count in loop is = \(researches.count)")
}
success(researches)
}catch let error as NSError{
print(error)
failure("Can be extract from NSERROR")
}
} else {
print(error)
failure("Error - Can be extract for NSERROR")
}})
task.resume()
}
And for call this Fuction use something like this:
Research.downloadAllResearches({ (objects:[Research]) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Do whatever you like with the content
})
}) { (failureLiteral:String) -> Void in
}