I am new to Swift/iOS development, so this might be a stupid question, but I can't seem to find how to do this correctly.
I am following a youtube guide on how to programmatically code (no storyboard) a login screen in Swift and register a user into a Firebase database. The basic outline of the design are as follows
Once the user clicks the icon of the cat with the crown, the image picker comes up and they can select a profile picture and then register like normal. If the user does not select an image from the picker, the cat with the crown icon gets loaded as the profile picture by default.
What I have been trying to do is make it so a different picture (not the cat, and not shown to the user on the login page) named "nedstark" is set as the default profile picture that is stored in the database.
The program uses LoginController.swift and LoginController + handlers.swift files to make this happen. I tried to add a conditional to the picker where if nothing is selected, the default profile pic is set to a specific default user pic (different than the cat), but it doesn't work.
This is the code that stores the photo and other User info into the Database
let imageName = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).jpg")
if let profileImage = self.profileImageView.image, let uploadData = UIImageJPEGRepresentation(profileImage, 0.1) {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if let error = error {
print(error)
return
}
if let profileImageUrl = metadata?.downloadURL()?.absoluteString {
let values = ["name": name, "email": email, "profileImageUrl": profileImageUrl]
self.registerUserIntoDatabaseWithUID(uid, values: values as [String : AnyObject])
}
})
}
This is the code where I am trying to set the default profile pic
func handleSelectProfileImageView() {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
else { //Testing if default profile pic set
selectedImageFromPicker = UIImage(named: "nedstark")
}
if let selectedImage = selectedImageFromPicker {
profileImageView.image = selectedImage
}
else { //Testing if default profile pic set
profileImageView.image = UIImage(named: "nedstark")
}
dismiss(animated: true, completion: nil)
}
I
I know that this is a total noob question, but any help, tips, or pointers is greatly appreciated. Thanks!
You need to handle delegate method of cancel event. UIImagepickercontrolledelegate has another method
imagePickerControllerDidCancel()
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
imageView.image = UIImage(named: "nedstark")
dismiss(animated: true, completion: nil)
}
When you click on cancel button in pickerviewcontroller then didfinishPickingMedia function won't call. That's way your default image is not set when you tap on cancel button.
Related
I saved a UIImage to UserDefaults via .data with this code, where the key equals "petPhoto1":
#IBAction func addPhotoButton(_ sender: Any) {
let picker = UIImagePickerController()
picker.allowsEditing = false
picker.delegate = self
picker.mediaTypes = ["public.image"]
present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image.storeInUserDefaults(for: "petPhoto\(activePet)")
UserDefaults.standard.set("yes", forKey: "doesImageExist\(activePet)")
}
dismiss(animated: true, completion: nil)
}
(unrelated stuff in between)
extension UIImage {
func storeInUserDefaults(with compressionQuality: CGFloat = 0.8, for key: String) {
guard let data = self.jpegData(compressionQuality: compressionQuality) else { return }
let encodedImage = try! PropertyListEncoder().encode(data)
UserDefaults.standard.set(encodedImage, forKey: key)
}
}
Now when I erase it like this:
UserDefaults.standard.set(nil, forKey: "petPhoto1")
I can still see that "Documents & Data" for my app under Settings is still full with the same size as the original image, indicating that it didn't actually delete it, even though it no longer displays when it gets loaded back from UserDefaults.
Can anyone figure out a way to fix this? Thanks!
By the way, in case it helps, here is other code related to this issue:
The code I use in the ImageViewController that I display the image after saving it:
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
activePet = UserDefaults.standard.string(forKey: "activePet")! // activePet = 1 (confirmed with debugging with other, unrelated code
imageView.image = try? UIImage.loadFromUserDefaults(with: "petPhoto\(activePet)")
}
extension UIImage {
static func loadFromUserDefaults(with key: String) throws -> UIImage? {
let activePet = UserDefaults.standard.string(forKey: "activePet")!
guard let data = UserDefaults.standard.data(forKey: "petPhoto\(activePet)") else {
return nil
}
do {
let decodedImageData = try PropertyListDecoder().decode(Data.self, from: data)
return UIImage(data: decodedImageData)
} catch let error {
throw error
}
}
}
When you do this:
UserDefaults.standard.set(nil, forKey: "petPhoto1")
The link between the key and the file saved will be removed synchronously. that means if you try to access the value for this key, it gives nil.
But this image needs to be cleared from storage too, that will be happening asynchronously [we don't have completion handler API support from apple to get this information].
Apple Documentation for reference:
At runtime, you use UserDefaults objects to read the defaults that
your app uses from a user’s defaults database. UserDefaults caches the
information to avoid having to open the user’s defaults database each
time you need a default value. When you set a default value, it’s
changed synchronously within your process, and asynchronously to
persistent storage and other processes.
What you can try:
First approch
Give some time for the file delete operation to get completed by OS. then try to access the image in the disk.
Second approch
Try observing to the changes in the directory using GCD. Refer: https://stackoverflow.com/a/26878163/5215474
In iOS Swift I'm having difficulty loading an animated GIF from the device photo library to a UIImageView or creating a .gif file from it. I have no problem displaying the animated GIF if it's a file on a server or if it's right in the app. However, when I load it from the photo library, I lose the animation. I found some Objective-C solutions and tried to convert them over to Swift, but haven't had any luck.
Here is what I have so far:
#IBAction func buttonTapped(sender: UIButton) {
selectPhoto()
}
func selectPhoto() {
let photoLibraryController = UIImagePickerController()
photoLibraryController.delegate = self
photoLibraryController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
let mediaTypes:[String] = [kUTTypeImage as String]
photoLibraryController.mediaTypes = mediaTypes
photoLibraryController.allowsEditing = false
self.presentViewController(photoLibraryController, animated: true, completion: nil)
}
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let origImage = info[UIImagePickerControllerOriginalImage] as! UIImage
self.imageView.image = origImage
picker.dismissViewControllerAnimated(true, completion: nil)
}
Optimally, I would like to save the photo library image to a gif file on the server. From there I have no trouble displaying it. However, I need some help getting the data from the photo library into some type of property, without losing the animation. Do I need to use something like ALAssetsLibrary for this?
Thanks.
Yes, Use ALAssetsLibrary → now called PHAsset.
You should get the NSData of the gif, not UIImage( because UIImage will only get the first frame.)
So basically you would do something like this:
One you get the asset
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true // adjust the parameters as you wish
PHImageManager.default().requestImageData(for: asset, options: requestOptions, resultHandler: { (imageData, UTI, _, _) in
if let uti = UTI,let data = imageData ,
// you can also use UTI to make sure it's a gif
UTTypeConformsTo(uti as CFString, kUTTypeGIF) {
// save data here
}
})
Resource: PHAsset
You can get an access to the location of the image file using InfoKey .imageURL and retrieve Data from this URL
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let path = info[.imageURL] as? URL {
if let data = try? Data(contentsOf: path) {
self.imageView.image = UIImage(data: data)
//Now you have Data of a file: so you can convert it to image or gif or send to server or start animation (e.g. Framework JellyGIF)
self.imageView.startGif(with: .data(data))
}
picker.dismissViewControllerAnimated(true, completion: nil)
}
In iOS Swift I'm having difficulty loading an animated GIF from the device photo library to a UIImageView or creating a .gif file from it. I have no problem displaying the animated GIF if it's a file on a server or if it's right in the app. However, when I load it from the photo library, I lose the animation. I found some Objective-C solutions and tried to convert them over to Swift, but haven't had any luck.
Here is what I have so far:
#IBAction func buttonTapped(sender: UIButton) {
selectPhoto()
}
func selectPhoto() {
let photoLibraryController = UIImagePickerController()
photoLibraryController.delegate = self
photoLibraryController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
let mediaTypes:[String] = [kUTTypeImage as String]
photoLibraryController.mediaTypes = mediaTypes
photoLibraryController.allowsEditing = false
self.presentViewController(photoLibraryController, animated: true, completion: nil)
}
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let origImage = info[UIImagePickerControllerOriginalImage] as! UIImage
self.imageView.image = origImage
picker.dismissViewControllerAnimated(true, completion: nil)
}
Optimally, I would like to save the photo library image to a gif file on the server. From there I have no trouble displaying it. However, I need some help getting the data from the photo library into some type of property, without losing the animation. Do I need to use something like ALAssetsLibrary for this?
Thanks.
Yes, Use ALAssetsLibrary → now called PHAsset.
You should get the NSData of the gif, not UIImage( because UIImage will only get the first frame.)
So basically you would do something like this:
One you get the asset
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true // adjust the parameters as you wish
PHImageManager.default().requestImageData(for: asset, options: requestOptions, resultHandler: { (imageData, UTI, _, _) in
if let uti = UTI,let data = imageData ,
// you can also use UTI to make sure it's a gif
UTTypeConformsTo(uti as CFString, kUTTypeGIF) {
// save data here
}
})
Resource: PHAsset
You can get an access to the location of the image file using InfoKey .imageURL and retrieve Data from this URL
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let path = info[.imageURL] as? URL {
if let data = try? Data(contentsOf: path) {
self.imageView.image = UIImage(data: data)
//Now you have Data of a file: so you can convert it to image or gif or send to server or start animation (e.g. Framework JellyGIF)
self.imageView.startGif(with: .data(data))
}
picker.dismissViewControllerAnimated(true, completion: nil)
}
Besides browsing the Photolibrary I've enabled the Camera to add a photo to my
#IBAction func startCameraButtonTapped(sender: AnyObject) {
let startCameraController = UIImagePickerController()
startCameraController.delegate = self
startCameraController.sourceType = UIImagePickerControllerSourceType.Camera
startCameraController.allowsEditing = true
self.presentViewController(startCameraController, animated: true, completion: nil)
//invoiceImageHolder.image!.scaleAndRotateImage2(280)
//self.invoiceImageHolder.transform = CGAffineTransformMakeRotation((180.0 * CGFloat(M_PI)) / 180.0)
}
Every user input can be deleted, but now I'd like to know where the corresponding photo is. When I browse the photo library I can't see the photos taken with my app? Where are those photos stored?
[edit]
This is my saveImage process
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
saveImage(selectedImage, path: fileInDocumentsDirectory("\(uniqueImageName)"))
// set the unique image reference in textfield
// Set photoImageView to display the selected image
self.invoiceImageHolder.image = selectedImage
// Dismiss the picker
dismissViewControllerAnimated(true, completion: nil)
}
func saveImage(image: UIImage, path: String) -> Bool{
let pngImageData = UIImagePNGRepresentation(image)
// let jpgImageData = UIImageJPEGRepresentation(image, 1.0)
let result = pngImageData?.writeToFile(path, atomically: true)
//print("Save result \(result)")
return result!
}
EDIT hmm I think I see where the photos are stored, but now how do I browse to them for deletions?
I think it's here:
/var/mobile/Containers/Data/Application/CBFE9E9D-A657-4011-8B9F-EF0C9BB2C603/Documents/
BUT everytime i restart the app the part between Application and /Documents is different??
Still not a working solution, but for those newbies as me this is a valuable readup: http://www.techotopia.com/index.php/Working_with_Directories_in_Swift_on_iOS_8
[EDIT 2]
with this code i am able to see all whiles stored with my app. I was a bit surprise to see all the files
// Files stored in my app filemanager
let filemgr = NSFileManager.defaultManager()
do {
print("Start filemanager")
let filelist = try filemgr.contentsOfDirectoryAtPath(documentsDirectory())
for filename in filelist {
print(filename)
}
} catch {
print("No directory path \(error)")
}
// end filemanager
UIImagePickerController() doesn't automatically save your photos into the library. See its UIImagePickerControllerDelegate protocol and you'll see that it has a method returning your UIImage after making a photo. Then you can save it:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
you save them - not in the photo library - but as 'simply' files inside your app's documents folder. There is no standard UI for deletion then.
You need to write your own ViewController to show & handle deletions then.
I'm trying to post a photo to Facebook, and it works fine using the following code:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
self.selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
var photo = FBSDKSharePhoto()
photo.image = self.selectedImage
photo.userGenerated = true
var content = FBSDKSharePhotoContent()
content.photos = [photo]
self.dismissViewControllerAnimated(true, completion: nil)
shareDialog.fromViewController = self
shareDialog.shareContent = content
shareDialog.show()
}
However, this only works when the user has the facebook app downloaded. otherwise it does NOT open the web browser to share the photo, even though facebook state:
The Share Dialog switches to the native Facebook for iOS app, then
returns control to your app after a post is published. If someone
doesn't have Facebook app installed it will automatically falls back
to a web-based dialog.
Any suggestions please ? answers appreciated, thank you.
You can try this:
func postToFacebook(image: UIImage!) {
var SocialMedia :SLComposeViewController = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
SocialMedia.completionHandler = {
result -> Void in
var getResult = result as SLComposeViewControllerResult;
switch(getResult.rawValue) {
case SLComposeViewControllerResult.Cancelled.rawValue: println("Cancelled")
case SLComposeViewControllerResult.Done.rawValue: println("It's Work!")
default: println("Error!")
}
self.dismissViewControllerAnimated(true, completion: nil)
}
self.presentViewController(SocialMedia, animated: true, completion: nil)
SocialMedia.setInitialText("...")
SocialMedia.addImage(image)
}