This question already has answers here:
Save images in NSUserDefaults?
(13 answers)
Closed 9 months ago.
I'm trying to save users image to userDefaults but i'm always getting error "Thread 1: "Attempt to insert non-property list object (Function) for key savedimage" Here's my code
import UIKit
class productSelectionPage: UIViewController {
#IBOutlet weak var productImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Save image
let image = UIImage(named: "user")
let imageData = UIImage.jpegData(image)
UserDefaults.standard.setValue(imageData, forKey: "savedImage")
}
#IBAction func offerButtonTapped(_ sender: UIButton) {
// present image
let imageData = UserDefaults.standard.object(forKey: "savedImage")
let image = UIImage(data: (imageData as! NSData) as Data)
productImageView.image = image
// crash
}
}
Try to use PropertyListEncoder() and Decoder like this:
func saveImage() {
guard let data = UIImage(named: "image").jpegData(compressionQuality: 0.5) else { return }
let encoded = try! PropertyListEncoder().encode(data)
UserDefaults.standard.set(encoded, forKey: "KEY")
}
func loadImage() {
guard let data = UserDefaults.standard.data(forKey: "KEY") else { return }
let decoded = try! PropertyListDecoder().decode(Data.self, from: data)
let image = UIImage(data: decoded)
}
If you have ever used firebase, you would understand that all images and videos are saved as data, not as pixels.
A similar approach is taken when saving to UserDefaults
// storing the image data
private func store(image: UIImage, forKey key: String, withStorageType storageType: StorageType) {
if let pngRepresentation = image.pngData() {
UserDefaults.standard.set(pngRepresentation, forKey: key)
}
}
// retrieving the image data and getting an image
To retrieve it, we will take that image data
private func retrieveImage(forKey key: String, inStorageType storageType: StorageType) -> UIImage? {
if let imageData = UserDefaults.standard.object(forKey: key) as? Data,
let image = UIImage(data: imageData) {
return image
}
}
}
You can just simply convert Image into Data as shown in the extension for UIImageView like below code
//MARK: -|||-To Store Image in Userdefault You can just Simply create an uiimageview extension-||| -
extension UIImageView{
func saveImage(){
guard let image = self.image, let data = image.jpegData (compressionQuality: 0.5) else { return }
let encoded = try! PropertyListEncoder ().encode (data)
UserDefaults.standard.set(encoded, forKey: "image")
}
func loadImage() {
guard let data = UserDefaults.standard.data(forKey: "image") else { return }
let decoded = try! PropertyListDecoder().decode(Data.self, from: data)
let image = UIImage(data: decoded)
self.image = image
}
}
Here is how to use this extension:
class ViewController: UViewController {
#IBOutlet var sendImg: UIImageView!
#IBOutlet var receiveImg: UlImageView!
override func viewDidLoad(){
super.viewDidLoad() {
sendImg.saveImage()
}
#IBAction func loadImageFromUserDefault(_ sender: UIButton) {
receiveImg.loadImage()
}
}
to save image in UserDefault:
yourImage.saveImage()
to get image from UserDefault:
yourImage.loadImage()
Related
I have tableview with label, imageView (for image, gif & video thumbnail). I am sure that doing something wrong and I can't handle its completion handler due to which the app is hanged and gets stuck for a long time.
My model is like,
struct PostiisCollection {
var id :String?
var userID: String?
var leadDetails : NSDictionary?
var company: NSDictionary?
var content: String?
init(Doc: DocumentSnapshot) {
self.id = Doc.documentID
self.userID = Doc.get("userID") as? String ?? ""
self.leadDetails = Doc.get("postiiDetails") as? NSDictionary
self.company = Doc.get("company") as? NSDictionary
self.content = Doc.get("content") as? String ?? ""
}
}
I wrote in my view controller for fetch this,
var postiisCollectionDetails = [PostiisCollection]()
override func viewDidLoad() {
super.viewDidLoad()
let docRef = Firestore.firestore().collection("PostiisCollection").whereField("accessType", isEqualTo: "all_access")
docRef.getDocuments { (querysnapshot, error) in
if let doc = querysnapshot?.documents, !doc.isEmpty {
print("Document is present.")
for document in querysnapshot!.documents {
_ = document.documentID
if let compCode = document.get("company") as? NSDictionary {
do {
let jsonData = try JSONSerialization.data(withJSONObject: compCode)
let companyPost: Company = try! JSONDecoder().decode(Company.self, from: jsonData)
if companyPost.companyCode == AuthService.instance.companyId ?? ""{
print(AuthService.instance.companyId ?? "")
if (document.get("postiiDetails") as? NSDictionary) != nil {
let commentItem = PostiisCollection(Doc: document)
self.postiisCollectionDetails.append(commentItem)
}
}
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.async {
self.tableView.isHidden = false
self.tableView.reloadData()
}
}
}
}
}
}
I need to check for the index path with image view is either image or gif or video with different parameters, I tried with tableview delegate and datasource method by,
extension AllAccessPostiiVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postiisCollectionDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AllAccessCell", for: indexPath)
let label1 = cell.viewWithTag(1) as? UILabel
let imagePointer = cell.viewWithTag(3) as? UIImageView
let getGif = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "video") as? NSArray
label1?.text = "\(arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "title"))"
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
print(arrGif[0])
let gifURL : String = "\(arrGif[0])"
let imageURL = UIImage.gifImageWithURL(gifURL)
imagePointer?.image = imageURL
playButton?.isHidden = true
}
if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
print(arrPhoto[0])
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
DispatchQueue.main.async {
imagePointer?.image = image
playButton?.isHidden = true
}
} catch {
print(error)
}
})
}
if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL = URL(string: arrVideo[0])
let asset = AVAsset(url:videoURL!)
if let videoThumbnail = asset.videoThumbnail{
SVProgressHUD.dismiss()
imagePointer?.image = videoThumbnail
playButton?.isHidden = false
}
}
}
}
If I run, the app hangs in this page and data load time is getting more, some cases the preview image is wrongly displayed and not able to handle its completion
As others have mentioned in the comments, you are better of not performing the background loading in cellFroRowAtIndexPath.
Instead, it's better practice to add a new method fetchData(), where you perform all the server interaction.
So for example:
// Add instance variables for fast access to data
private var images = [UIImage]()
private var thumbnails = [UIImage]()
private func fetchData(completion: ()->()) {
// Load storage URLs
var storageURLs = ...
// Load data from firebase
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
// Parse data and store resulting image in image array
...
// Call completion handler to indicate that loading has finished
completion()
})
}
Now you can call fetchData() whenever you would like to refresh data and call tableview.reloadData() within the completion handler. That of course must be done on the main thread.
This approach simplifies your cellForRowAtIndexPath method.
There you can just say:
imagePointer?.image = ...Correct image from image array...
Without any background loading.
I suggest using below lightweight extension for image downloading from URL
using NSCache
extension UIImageView {
func downloadImage(urlString: String, success: ((_ image: UIImage?) -> Void)? = nil, failure: ((String) -> Void)? = nil) {
let imageCache = NSCache<NSString, UIImage>()
DispatchQueue.main.async {[weak self] in
self?.image = nil
}
if let image = imageCache.object(forKey: urlString as NSString) {
DispatchQueue.main.async {[weak self] in
self?.image = image
}
success?(image)
} else {
guard let url = URL(string: urlString) else {
print("failed to create url")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
// response received, now switch back to main queue
DispatchQueue.main.async {[weak self] in
if let error = error {
failure?(error.localizedDescription)
}
else if let data = data, let image = UIImage(data: data) {
imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.image = image
success?(image)
} else {
failure?("Image not available")
}
}
}
task.resume()
}
}
}
Usage:
let path = "https://i.stack.imgur.com/o5YNI.jpg"
let imageView = UIImageView() // your imageView, which will download image
imageView.downloadImage(urlString: path)
No need to put imageView.downloadImage(urlString: path) in mainQueue, its handled in extension
In your case:
You can implement following logic in cellForRowAt method
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let urlString : String = "\(arrGif[0])"
let image = UIImage.gifImageWithURL(urlString)
imagePointer?.image = image
playButton?.isHidden = true
}
else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let urlString = Storage.storage().reference(forURL: arrPhoto[0])
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = true
}
elseif getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let urlString = arrVideo[0]
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = false
}
If you have one imageView to reload in tableView for photo, video and gif. then use one image array to store it prior before reloading. So that your main issue of hang or stuck will be resolved. Here the main issue is each time in table view cell collection data is being called and checked while scrolling.
Now I suggest to get all photo, gifs and video (thumbnail) as one single array prior to table view reload try this,
var cacheImages = [UIImage]()
private func fetchData(completionBlock: () -> ()) {
for (index, _) in postiisCollectionDetails.enumerated() {
let getGif = postiisCollectionDetails[index].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = postiisCollectionDetails[index].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = postiisCollectionDetails[index].leadDetails?.value(forKey: "video") as? NSArray
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let gifURL : String = "\(arrGif[0])"
self.randomList.append(gifURL)
/////---------------------------
let imageURL = UIImage.gifImageWithURL(gifURL)
self.cacheImages.append(imageURL!)
//////=================
}
else if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL: String = "\(arrVideo[0])"
let videoUrl = URL(string: arrVideo[0])
let asset = AVAsset(url:videoUrl!)
if let videoThumbnail = asset.videoThumbnail{
////--------------
self.cacheImages.append(videoThumbnail)
//-----------
}
self.randomList.append(videoURL)
}else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let photoURL : String = "\(arrPhoto[0])"
/////---------------------------
let url = URL(string: photoURL)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let image = UIImage(data: imageData)
if image != nil {
self.cacheImages.append(image!)
}
else {
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
}
}
//////=================
}
else {
//-----------------
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
//--------------------
}
}
completionBlock()
}
After getting all UIImage as array where loop is being called. Now you call this function inside your viewDidLoad. So after all values in images fetched then try to call tableView like this,
override func viewDidLoad() {
self.fetchData {
DispatchQueue.main.async
self.tableView.reloadData()
}
}
}
Now atlast, you may use SDWebImage or any other background image class or download method to call those in tableView cellforRow method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// your cell idetifier & other stuffs
if getVideo != nil {
imagePointer?.image = cacheImages[indexPath.row]
playButton?.isHidden = false
}else {
imagePointer?.image = cacheImages[indexPath.row]
// or get photo with string via SdWebImage
// imagePointer?.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "edit-user-80"))
playButton?.isHidden = true
}
return cell
}
You're handling data in a totally wrong manner. Data(contentsOf: url!) - This is wrong. You should chache the images and should download it to directory. When you convert something into data it takes place into the memory(ram) and it is not good idea when playing with large files. You should use SDWebImage kind of library to set images to imageview.
Second thing if let videoThumbnail = asset.videoThumbnail - This is also wrong. Why you're creating assets and then getting thumbnail from it? You should have separate URL for the thumbnail image for your all videos in the response of the API and then again you can use SDWebImage to load that thumbnail.
You can use SDWebImage for gif as well.
Alternative of SDWebImage is Kingfisher. Just go through both libraries and use whatever suitable for you.
The code here allows me to upload and download one photo to Firebase and save it to user defaults but I'm trying to figure out how to do it within a collectionView cell and display as many photos wanted, adding on new items
import UIKit
import FirebaseStorage
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
private let storage = Storage.storage().reference()
#IBOutlet var imageView: UIImageView!
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label.numberOfLines = 0
guard let urlString = UserDefaults.standard.value(forKey: "url") as? String, let url = URL(string: urlString) else {
return
}
label.text = urlString
let task = URLSession.shared.dataTask(with: url, completionHandler: { data,_,error in
guard let data = data, error == nil else {
return
}
DispatchQueue.main.async {
let image = UIImage(data: data)
self.imageView.image = image
}
})
task.resume()
}
#IBAction func didTapButton() {
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
return
}
guard let imageData = image.pngData() else {
return
}
storage.child("Images/Photo.png").putData(imageData, metadata: nil) { (_, error) in
guard error == nil else {
print("Failed to Upload Data")
return
}
self.storage.child("Images/Photo.png").downloadURL(completion: {url, error in
guard let url = url, error == nil else {
return
}
let urlString = url.absoluteString
DispatchQueue.main.async {
self.label.text = urlString
self.imageView.image = image
}
print("Download URL: \(urlString)")
UserDefaults.standard.set(urlString, forKey: "url")
})
}
// Upload Image Data
// Get Download URL
// Save Download URL to userDefaults
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
To upload images to Firebase storage and show them in a collection view, you can use the following steps;
Set up collection view with an array of URLs (or Strings) as its
data source. You can use your custom models if required.
Keep a reference to your Firebase storage and upload the image. After successful upload, get the URL for the uploaded image using the image reference.
Save the url in Firebase Database(or Cloud Firestore). This is required only if you want to sync the collection view with the database and update it when new images are uploaded.
Add a listener to your Firebase database reference where you have
saved the image URLs. Update the local URLs array inside the listener and reload the collection view.
If you don't want to use Firebase database, omit steps 3 and 4, save the image URL to the array and reload the collection view right away.
I'm not adding the code for collection view setup here as it's not the objective of this answer.
let storageRef = Storage.storage().reference(withPath: "images")
let databaseRef = Database.database().reference(withPath:"images")
var images: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
addDatabaseListener()
}
private func addDatabaseListener() {
databaseRef.observe(.childAdded) { (snapshot) in
guard let value = snapshot.value as? [String: Any], let url = value["url"] as? String else { return }
self.images.append(url)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage, let data = image.jpegData(compressionQuality: 0.1) else { return }
let fileName = "\(Date.timeIntervalSinceReferenceDate).jpeg"
let newImageRef = storageRef.child(fileName)
newImageRef.putData(data, metadata: nil) { (_, error) in
if let error = error {
print("upload failed: ", error.localizedDescription)
return
}
newImageRef.downloadURL { (url, error) in
if let error = error {
print("error: ", error.localizedDescription)
return
}
self.databaseRef.childByAutoId().setValue(["url": url?.absoluteString])
}
}
}
I'm building an app that needs an image slideshow as the background of the welcome controller. My plan is to import images into a folder in Firebase Storage, set a Service function to download the folder's images and append to a model, then populate the controller's collection view cell with the images. Am I on the correct path to do an image slideshow? Thanks.
// BackgroundImage Model
struct BackgroundImage {
let backgroundImageUrl: String
init(dictionary: [String : Any]) {
self.backgroundImageUrl = dictionary["backgroundImageUrl"] as? String ?? ""
}
}
// Service
struct BgImgs {
let backgroundImage: UIImage
}
static func fetchBackgroundImages(bgImgs: BgImgs, completion: #escaping([BackgroundImage]) -> Void) {
var backgroundImages = [BackgroundImage]()
guard let imageData = bgImgs.backgroundImage.jpegData(compressionQuality: 0.3) else { return }
let filename = NSUUID().uuidString
let storageRef = STORAGE_REF.reference(withPath: "/background_images/\(filename)")
storageRef.putData(imageData, metadata: nil) { (meta, error) in
storageRef.downloadURL { (url, error) in
guard let backgroundImageUrl = url?.absoluteString else { return }
let values = ["backgroundImageUrl" : backgroundImageUrl]
let bgImages = BackgroundImage(dictionary: values)
backgroundImages.append(bgImages)
completion(backgroundImages)
}
}
}
// WelcomeController
private var backgroundImages = [BackgroundImage]()
func fetchBackgroundImages() {
Service.fetchBackgroundImages(bgImgs: backgroundImages) { backgroundImages in
self.backgroundImages = backgroundImages
}
}
You'll have to reload the UICollectionView after fetchBackgroundImages fetch is done.
func fetchBackgroundImages() {
Service.fetchBackgroundImages(bgImgs: backgroundImages) { backgroundImages in
self.backgroundImages = backgroundImages
self.collectionView.reloadData()
}
}
[][!I want load image from download url from firebase using image cache how can I can create an inheritance where my image gets loaded I guess I am missing OOP concept somewhere. my image view is swipe label, how too create an instance where I can display the image on my view.
import UIKit
import Firebase
class swipeLabelViewController: UIViewController {
#IBOutlet weak var UserAgeText: UILabel!
var user:User? {
didSet {
let userNameSwipe = user?.userName
userNameLabel.text = userNameSwipe
let userAgeSwipe = user?.userAge
UserAgeText.text = userAgeSwipe
guard let profileImageUrl = user?.profileImageUrl else {
return }
profileImageView.loadImage(with: profileImageUrl)
print(profileImageUrl)
// let userFetchedImage = user?.profileImageUrl
// swipeLabel.image = userFetchedImage
// self.swipeLabel.image = UIImage(contentsOfFile: profileImageUrl)
}
}
var profileImageView: CustomImageView = {
let iv = CustomImageView()
return iv
}()
var imageI : UIImage!
// var swipepic = CustomImageView()
#IBOutlet weak var swipeLabel: UIImageView!
#IBOutlet weak var userNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
swipeLabel.image = imageI
let gesture = UIPanGestureRecognizer(target: self, action:
selector(wasDragged(gestureRecognizer:)))
swipeLabel.addGestureRecognizer(gesture)
fetchCurrentUserData()
}
// user model class``
class User {
// attributes getting users info so i can set up from firebase
var userName:String!
var userAge:String!
var uid:String!
var profileImageUrl: String!
init (uid:String,dictionary:Dictionary) {
self.uid = uid
if let userName = dictionary [ "userName" ] as? String{
self.userName = userName
}
if let userAge = dictionary [ "userAge" ] as? String{
self.userAge = userAge
}
if let profileImageUrl = dictionary["profileImageURL"] as? String {
self.profileImageUrl = profileImageUrl
}
}
}
// and lastly image cache class customImageView
import Foundation
import UIKit
var imageCache = String: UIImage
class CustomImageView: UIImageView {
var lastImgUrlUsedToLoadImage: String?
func loadImage(with urlString: String) {
// set image to nil
self.image = nil
// set lastImgUrlUsedToLoadImage
lastImgUrlUsedToLoadImage = urlString
// check if image exists in cache
if let cachedImage = imageCache[urlString] {
self.image = cachedImage
return
}
// url for image location
guard let url = URL(string: urlString) else { return }
// fetch contents of URL
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to load image with error",
error.localizedDescription)
}
if self.lastImgUrlUsedToLoadImage != url.absoluteString {
return
}
// image data
guard let imageData = data else { return }
// create image using image data
let photoImage = UIImage(data: imageData)
// set key and value for image cache
imageCache[url.absoluteString] = photoImage
// set image
DispatchQueue.main.async {
self.image = photoImage
}
}.resume()
}
}
no errors I just can't see image on my swipelabel UIImage
You need to make a separate request to download the image from the url that firebase returns. Here is a playground example:
//: A SpriteKit based Playground
import PlaygroundSupport
import UIKit
enum ImageDownloadError: Error {
case failedToConvertDataToImage
}
func dowloadImage(from url: URL, completion: #escaping (Result<UIImage, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let image = data.flatMap(UIImage.init(data:)) else {
return completion (.failure(error ?? ImageDownloadError.failedToConvertDataToImage))
}
completion(.success(image))
}.resume()
}
let imageView = UIImageView()
dowloadImage(from: URL(string: "https://res.cloudinary.com/demo/image/upload/sample.jpg")!) { [weak imageView] result in
switch result {
case .failure(let error):
print(error.localizedDescription)
case .success(let image):
imageView?.image = image
}
}
imageView.frame = .init(origin: .zero, size: .init(width: 300, height: 300))
PlaygroundPage.current.liveView = imageView
PlaygroundPage.current.needsIndefiniteExecution = true
N.B: This is not a repeat question, I have looked at the other solutions but none are working in Swift 3, but they do in Swift 2.
I need to get an image from a photo library with UIImagePickerController, that is working fine, then what I need to do, is to somehow save that to a public record in CloudKit, I am very open as to how this is done. Please, if possible, be clear as to what I need to change/add and where.
I have provided my entire view controller file just to be sure.
import UIKit
import CloudKit
var email: String = ""
class ViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//MARK: Properties
#IBOutlet weak var firstNameField: UITextField!
#IBOutlet weak var lastNameField: UITextField!
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
#IBOutlet weak var confirmPasswordField: UITextField!
#IBOutlet weak var notMatching: UILabel!
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var errorLabel: UILabel!
#IBOutlet weak var photoImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.firstNameField.autocorrectionType = .no
self.lastNameField.autocorrectionType = .no
self.emailField.autocorrectionType = .no
self.passwordField.autocorrectionType = .no
self.confirmPasswordField.autocorrectionType = .no
self.notMatching.isHidden = true
firstNameField.delegate = self
lastNameField.delegate = self
emailField.delegate = self
passwordField.delegate = self
confirmPasswordField.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: UIImagePickerControllerDelegate
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
// Dismiss the picker if the user canceled.
dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// The info dictionary may contain multiple representations of the image. You want to use the original.
guard let selectedImage = info[UIImagePickerControllerOriginalImage] as? UIImage else {
fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
}
// Set photoImageView to display the selected image.
photoImageView.image = selectedImage
// Dismiss the picker.
dismiss(animated: true, completion: nil)
}
//MARK: Actions
#IBAction func selectImageFromPhotoLibrary(_ sender: UITapGestureRecognizer) {
//Hide keyboards
firstNameField.resignFirstResponder()
lastNameField.resignFirstResponder()
emailField.resignFirstResponder()
passwordField.resignFirstResponder()
confirmPasswordField.resignFirstResponder()
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
present(imagePickerController, animated: true, completion: nil)
}
#IBAction func signUpPressed(_ sender: UIButton) {
let container = CKContainer.default()
let pubDB = container.publicCloudDatabase
//let privDB = container.privateCloudDatabase
//Check if users exist
let query = CKQuery(recordType: "MyUsers", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
pubDB.perform(query, inZoneWith: nil, completionHandler: { (records, error) in
//error code 11 is no objects found
if error == nil || error?._code == 11 {
var emailExists = false
for record in records! {
if record.object(forKey: "email") as? String == self.emailField.text {
//other user with the same username exists - don't allow user to create account
emailExists = true
}
}
if emailExists == true {
self.emailLabel.text = "\(self.emailField.text!) is taken. Please choose another one."
} else {
if self.firstNameField.text != nil && self.lastNameField.text != nil && self.passwordField.text == self.confirmPasswordField.text {
//user can sign up
let record = CKRecord(recordType: "MyUsers")
record.setObject(self.emailField.text! as CKRecordValue?, forKey: "email")
record.setObject(self.passwordField.text! as CKRecordValue?, forKey: "password")
record.setObject(self.firstNameField.text! as CKRecordValue?, forKey: "firstName")
record.setObject(self.lastNameField.text! as CKRecordValue?, forKey: "lastName")
print("all good")
pubDB.save(record, completionHandler: { (record, error) in
if error == nil {
OperationQueue.main.addOperation {
UserDefaults.standard.set(self.emailField.text!, forKey: "Email")
email = self.emailField.text!
//self.performSegue(withIdentifier: "Games", sender: self)
}
} else {
print(error)
}
})
} else {
}
}
} else {
print(error)
}
})
}
// MARK: UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}
}
Thank you!
Here is a simple way to save an image as a CKAsset with CloudKit. Please make sure to change the name for your Record, and the field name for the asset from when you set up the record.
let documentsDirectoryPath:NSString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
var imageURL: URL!
let tempImageName = "Image2.jpg"
func saveImage(_ image: UIImage?) {
// Create a CKRecord
let newRecord:CKRecord = CKRecord(recordType: "<INSERT_RECORD_NAME")
if let image = image {
let imageData:Data = UIImageJPEGRepresentation(image, 1.0)!
let path:String = self.documentsDirectoryPath.appendingPathComponent(self.tempImageName)
try? UIImageJPEGRepresentation(image, 1.0)!.write(to: URL(fileURLWithPath: path), options: [.atomic])
self.imageURL = URL(fileURLWithPath: path)
try? imageData.write(to: self.imageURL, options: [.atomic])
let File:CKAsset? = CKAsset(fileURL: URL(fileURLWithPath: path))
newRecord.setObject(File, forKey: "<INSERT_RECORD_ASSET_FIELD_NAME")
}
if let database = self.publicDatabase {
database.save(newRecord, completionHandler: { (record:CKRecord?, error:Error?) in
// Check if there was an error
if error != nil {
NSLog((error?.localizedDescription)!)
}
else if let record = record {
// Do whatever you want with the record, but image record was saved, asset should be saved.
}
})
}
}
If you can't do JPEG format, and need to save as .png you can substitute the UIImageJPEGRepresentation section with this:
let imageData:Data = UIImagePNGRepresentation(image)!
try? UIImagePNGRepresentation(image)!.write(to: URL(fileURLWithPath: path), options: [.atomic])
And make the tempImageName something like let tempImageName = "Image2.png"
Hope this helps
Swift 5 version, but abstracted to a function where I can pass in a UI Image object and return the URL to be used. Useful where I had the image saved in CoreData and wanted to later upload to CloudKit as well.
func getImageURL(for image: UIImage?) -> URL {
let documentsDirectoryPath:NSString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let tempImageName = "tempImage.jpg"
var imageURL: URL?
if let image = image {
let imageData:Data = image.jpegData(compressionQuality: 1.0)!
let path:String = documentsDirectoryPath.appendingPathComponent(tempImageName)
try? image.jpegData(compressionQuality: 1.0)!.write(to: URL(fileURLWithPath: path), options: [.atomic])
imageURL = URL(fileURLWithPath: path)
try? imageData.write(to: imageURL!, options: [.atomic])
}
return imageURL!
}
And then how I use it:
let imageURL = getImageURL(for: UIImage(data: itemToPublish!.Photo!)!)
let imageAsset = CKAsset(fileURL: imageURL)
itemBeingUpdated["photo"] = imageAsset
CKContainer.default().publicCloudDatabase.save(challengeRecord) { ...