I create/update contacts, using CNMutableContact.
I can set new image via imageData property, but I need to set custom crop information for creating thumbnail. Property thumbnailImageData is read-only.
Code:
let cnContact = CNMutableContact()
cnContact.imageData = imageData //created before
How to add custom thumbnailImage crop?
It seems that setting a thumbnail is not possible in iOS. However, by definition, a thumbnail of an image is the same image cropped to a smaller dimension. Hence iOS will auto generate the thumbnail from the image data set on the contact while saving the contact.
If you want to setup a different images for thumbnail and actual contact image, iOS will not allow you to do this.
Problem I have:
Before adding a new contact (CNMutableContact reference) in the user's contacts, I want to display the contact to the user. I can use the imageData to setup the new contact's image. However, when using CNContactViewController to display this new contact, the image is not cropped as per thumbnail. The thumbnail image showed looks super weird and scaled. How to resolve this?
Solution:
This occurs because the thumbnailImageData property on the CNMutableContact object is nil. This property cannot be set by the developers. This property can only be set by iOS internals and is auto generated by iOS while saving the contact.
So, before displaying the CNMutableContact object, you should save it to the users contacts, to kick in the auto-thumbnail-generation, and then immediately delete the contact.
The following extension on CNMutableContact depicts what you can do to achieve this.
extension CNMutableContact {
func generateThumbnailImage() {
if self.thumbnailImageData != nil {
return
}
// contact.thumbnailImageData is nil
// First save the contact for the thumbnail to be generated
let saveRequest = CNSaveRequest()
saveRequest.add(self, toContainerWithIdentifier: nil)
do {
try CNContactStore().execute(saveRequest)
} catch let error {
print("Error occurred while saving the request \(error)")
}
// self.thumbnailImageData is not nil. Contact Store will generate the thumbnail for this contact with the imageData provided.
// Now delete the contact
let deleteRequest = CNSaveRequest()
deleteRequest.delete(self)
do {
try CNContactStore().execute(deleteRequest)
} catch let error {
print("Error occurred while deleting the request \(error)")
}
// The contact is removed from the Contact Store
// However, the contact.thumbnailImageData is not nil anymore. Contacts Store has generated the thumbnail automatically with the imageData provided.
}
}
Related
My Objective-C iOS app schedules a local notification that has a userInfo dictionary and one small JPEG image attachment.
The image is attached like this:
content.attachments = #[[UNNotificationAttachment attachmentWithIdentifier:myIdentifier
URL:[imageURL filePathURL]
options:#{UNNotificationAttachmentOptionsTypeHintKey : UTTypeJPEG}
error:&error]];
This works fine. The notification is correctly scheduled.
If I ignore the Watch and let the notification appear on my phone's Lock Screen, the image is there.
Going back to the Watch. The Watch app receives the notification, and the didReceive method is called in the NotificationController.
No matter what I try, I can't get the image from the notification.
I've tried converting the image to NSData and adding it to the userInfo dictionary, but the data is too large despite the image actually being only a few Kb.
NotificationController.swift: (image is an Image type, and is sent to the NotificationView to use as the background.)
guard let attachment = notification.request.content.attachments.first
else {
print("Couldn't get the first attachment, using default image")
image = Image.init(kDefaultImage)
return
}
// We get here, so we know there's an attachment
if attachment.url.startAccessingSecurityScopedResource() {
let imageData = try? Data.init(contentsOf: attachment.url)
if let imageData = imageData {
image = Image(uiImage: UIImage(data: imageData) ??
UIImage.init(imageLiteralResourceName: kDefaultImageMasked))
}
attachment.url.stopAccessingSecurityScopedResource()
} else {
print("Couldn't access the file, using default")
image = Image.init(kDefaultImageMasked)
}
I always get told I can't access the file in the security scoped bit.
If I take out that check I get Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value because the code doesn't put anything into image.
Obviously I would put the default image in there instead of crashing, but once the Watch app crashes the notification that's shown on the Watch shows the correct image, but it's obviously not using my NotificationView because that crashed. It's using the standard mirror feature to show the notification, so it's completely bypassing my code at that point.
I've also tried this:
if(attachment.url.isFileURL) {
print("attachment image is a file url")
}
It does print out that it's a file url, and it's: file:///var/mobile/Library/BulletinDistributor/Attachments/com.mycompany.myapp/<UUID>.jpeg.
But this fails, so the file doesn't exist at that path, or I'm not allowed to load files from there (?):
if(FileManager.default.fileExists(atPath: attachment.url.path)) {
...
How do I get the image from the attachment? I've tried getting the image as Data, as an Image, as a UIImage. Nothing works.
This was simple to do in the old Objective-C WatchKit extension stuff:
NSArray *attachments = notification.request.content.attachments;
if(attachments.count == 1) {
[_backgroundGroup setBackgroundImage:attachments[0]];
}
No problems there; it always worked.
Any ideas?
I have a phonelist application that loads employees from a database and lets users search through them. They have an option to add the contact to their phone. Currently I can add all of the data to the phone as a CNMutableContact(), but if I open up my iOS contact application the image data only works the first time I open the application. If I open it later the images are gone, but all of the other information is correct. Is there something I need to do for saving the image? Does the image need to be stored locally aside from saving it through the save request? The CNMutableContact uses a Data object for the image data. Using Swift 4, iOS target 9.0+
let contact = CNMutableContact()
contact.givenName = givenName
contact.familyName = familyName
contact.imageData = UIImagePNGRepresentation(image)
let store = CNContactStore()
let saveRequest = CNSaveRequest()
saveRequest.add(contact, toContainerWithIdentifier: nil)
do {
try store.execute(saveRequest)
} catch let error {
print(error)
}
I am saving images to a custom album after either select or camera completion. Obviously, after camera completion, there is only one image, but when a user selects images in the gallery picker, in the completion handler, when I save that image to the custom album, a duplicate is ALWAYS created. Both in the gallery as well as the root photoAlbum. Everywhere, it seems. I cannot reference the ID to see if it was created before, because the ID is being newly created with the placeholder.
Is there a way to get the base image reference ID so that I can associate EVERY image to the original? As I understand it, IOS (I hate ios btw), saves only one actual image and the rest are just pointers to the original image object. If that is the case, I would expect there is a way to get a solid reference to the original Image and from there, I can easily manage assets created from that base image.
public static func addNewImage(_ image:UIImage, toAlbum albumName:String,imageID:String?,onSuccess success:#escaping(String)->Void, onFailure failure:#escaping(Error?)->Void) {
guard let album = self.getAlbum(withName: albumName) else {
failure(SDPhotosHelper.albumNotFoundError)
return
}
var localIdentifier = String();
if(imageID != nil){
if(self.hasImageInAlbum(withIdentifier: imageID!, fromAlbum: albumName)){
failure(SDPhotosHelper.albumNotFoundError)
return;
}
}
PHPhotoLibrary.shared().performChanges({
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let assetCreationRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
//assetCreationRequest.location = "";
let placeHolder = assetCreationRequest.placeholderForCreatedAsset
albumChangeRequest?.addAssets([placeHolder!] as NSArray)
if placeHolder != nil {
localIdentifier = (placeHolder?.localIdentifier)!
}
}) { (didSucceed, error) in
OperationQueue.main.addOperation({
didSucceed ? success(localIdentifier) : failure(error)
})
}
}
No one bothered to assist with this. Luckily, I was able to find the solution. For any who come across this or the similar one that was also sitting around with the crickets: Choosing a picture causes resave to camera roll here is a solution.
The code I have is to CREATE A NEW ASSET. It is useful only for the saving the image to your custom album after the user has taken a picture with the camera. It is for brand new assets.
However, for existing assets, you do not want to create a new asset. Instead, you want to add the existing asset to the custom album. To do this, you need a different method. Here is the code I created and it seems to be working. Keep in mind that you will have to get the asset ID FIRST, so that you can send it to your method and access the existing asset.
So, in your imagePickerController, you have to determine whether the user chose an existing image or whether the method is being called from a new camera action.
let pickerSource = picker.sourceType;
switch(pickerSource){
case .savedPhotosAlbum, .photoLibrary:
if(let url = info[UIIMagePickerControllerReferenceURL] as? NSURL{
let refURLString = refURL?.absoluteString;
/* value for refURLString looks something like assets-library://asset/asset.JPG?id=82A6E75C-EA55-4C3A-A988-4BF8C7F3F8F5&ext=JPG */
let refID = {function here to extract the id query param from the url string}
/*above gets you the asset ID, you can get the asset directly, but it is only
available in ios 11+.
*/
MYPHOTOHELPERCLASS.transferImage(toAlbum: "myalbumname", withID: refID!, ...)
}
break;
case .camera:
...
break;
}
Now, in your photohelper class (or in any function anywhere, whatever), to EDIT the asset instead of create a new one, this is what I have. I am assuming the changeRequest variable can be ommitted. I was just playing around until I got this right. Going through the completely ridiculous apple docs I was able to at least notice that there were other methods to play with. I found that the NSFastEnumeration parameter can be an NSArray of PHAssets, and not just placeholder PHObjectPlaceholder objects.
public static func transferImage(toAlbum albumName:String, withID imageID:String, onSuccess success:#escaping(String)->Void, onFailure failure:#escaping(Error?)->Void){
guard let album = self.getAlbum(withName: albumName) else{
... failure here, albumNotFoundError
return;
}
if(self.hasImageInAlbum(withIdentifier: imageID, fromAlbum: albunName)){
... failure here, image already exists in the album, do not make another
return;
}
let theAsset = self.getExistingAsset(withLocalIdentifier: imageID);
if(theAsset == nil){
... failure, no asset for asset id
return;
}
PHPhotoLibrary.shared().performChanges({
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album);
let changeRequest = PHAssetChangeRequest.init(for: theAsset!);
let enumeration:NSArray = [theAsset!];
let cnt = album.estimatedAssetCount;
if(cnt == 0){
albumChangeRequest?.addAssets(enumeration);
}else{
albumChangeRequest?.inserAssets(enumeration, at: [0]);
}
}){didSucceed, error) in
OperationQueue.main.addOperation({
didSucceed ? success(imageID) : failure(error);
})
}
}
So, it is pretty much the same, except instead of creating an Asset Creation Request and generating a placeholder for the created asset, you instead just use the existing asset ID to fetch an existing asset and add the existing asset to the addasset/insertasset NSArray parameter instead of a newly created asset
I want to retrieve the image that is stored in the storage of an user and place it next to his name in a custom UITableViewCell. The problem now is that the tableview will load when the images aren't done downloading (I think?), causing the application to crash because the image array is nil. So what is the correct way to load the tableview? I think, for the user experience, it is important that the tableviewcell image should be shown even if the images aren't done downloading, and present them a default image that is saved in the assists. I thought about making an array with UIImages that links to the default asset of loading an image and changing the image to the profile picture when it is done downloading. But I really have no clue how to do that. This is what I got so far about downloading the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference(forURL: "link.appspot.com")
channelRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let UIDs = each.value["userID"] as? String
if let allUIDS = UIDs{
let profilePicRef = storageRef.child((allUIDS)+"/profile_picture.png")
profilePicRef.data(withMaxSize: 1 * 500 * 500) { data, error in
if let error = error {
}
if (data != nil)
{
self.playerImages.append(UIImage (data: data!)!)
}
}
}
let userNames = each.value["username"] as? String
if let users = userNames{
self.players.append(users)
}
}
}
self.tableView.reloadData()
})
This is in the cellForRow
cell.playersImage.image = playerImages[indexPath.row] as UIImage
My rules, haven't changed it from the default rules:
service firebase.storage {
match /b/omega-towers-f5beb.appspot.com/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
Thank you.
Regarding user experience, you are correct. It is standard to have some sort of default image when loading an image from a URL. A great library to use for image caching and using default assets in its' place is AlamofireImage
Vandan Patel's answer is correct in saying you need to ensure your array is not nil when loading the tableview. You will be given a completion block to handle any extra work you would like to do with your image, using the AlamofireImage library.
This is all assuming you are getting a correct image URL back for your Firebase users.
You should call tableView.reloadData() when the images are done downloading. One important thing, initialize your playerImages as playerImages = [UIImage]() instead of playerImages: [UIImage]!. if it's empty, it wouldn't show your array is nil.
Update:
if let players = playerImages {
//code
}
I have successfully created an iMessage extension where the sender can choose an image, type text on top of the image and then send the new image and text combined to another recipient.
Currently, when the recipient taps the received message, it takes them to the iMessage App Store to download the app.
What I would like is for the recipient to NOT be redirected to the app store, but simply be presented with a larger view of the image they received.
Any help on how to achieve this (if possible) would be appreciated!
Edited:
After more research, I'm wondering if it's possible to send the newly created image (containing the image and text combined) as a MSSticker so when the user taps it, it simply enlarges?
Sending the image as a Message follows the MSMessage format (clicking on the message will direct a user to the app or app store). You can either send the image as a sticker or insert the image into the conversation!
(In case anyone's looking for belated enlightenment).
If you sent the image you create as an Attachment then it appears in the receiver's stream as just an image, without being associated with an app. Only when you send an MSMessage does the signature matching kick in and require the receiver to have the app.
See my im-plausibilities sample for a demo, specifically
func sendAttachment() {
guard let conversation = activeConversation else { fatalError("Expected a conversation") }
guard
let imageData = imageView?.image?.jpegData(compressionQuality: 0.8),
let docUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
else {
dismiss()
return
}
// WARNING this is not great practice - not robust if muliple messages sent without completing upload
attachmentPath = URL(fileURLWithPath: "imPhoto.jpg", relativeTo: docUrl)
if (try? imageData.write(to: attachmentPath!)) != nil {
conversation.insertAttachment(attachmentPath!, withAlternateFilename: "imPhoto.jpg") { (error) in
if let error = error {
os_log("Error with insertAttachment(message)")
print(error)
}
}
}
dismiss()
}