Swift Images to Cache - swift

I am parsing data and checking if an image is in cache. If yes, set it, if not set it to default and send it to cache.
The part that sends it to cache keeps giving nil ???
Any help would be appreciated
Pic_STR = "https://myserver/Pictures/" + Pic + ".jpg"
let url = URL(string: Pic_STR)
if let imageFromCache = imageCache.object(forKey: Pic_STR as AnyObject) as? UIImage {
data.photo = imageFromCache
return
} else {
data.photo = UIImage(named: "Default")
let imageCache = NSCache<NSString, AnyObject>()
URLSession.shared.dataTask(with: url!) { data, response, error in
guard let data = data, error == nil else { return }
let imageToCache = UIImage(data: data)
imageCache.setObject(imageToCache!, forKey: self.Pic_STR as NSString)
//self.image = imageToCache
}.resume()
}

After much experiminting I finally found a solution. I am sure it could be improved, but it works. I would prefer to use an extension, but can't figure out how.
struct imageData {
let image: UIImage?
let name: String?
}
var newImage = UIImage()
var myImages = [imageData]()
let imageString= "https://myserver/Pictures/" + Pic + ".jpg"
let imageURL = URL(string: imageString)
CON2 = false
for pics in myImages {
if pics.name == Pic {
data.photo = pics.image
CON2 = true
}
}
if CON2 == false {
let data2 = try? Data(contentsOf: imageURL!)
if let imageData = data2 {
newImage = UIImage(data: imageData)!
}
let newData = imageData(image: newImage, name: Pic) as imageData
myImages += [newData]
data.photo = newImage
}

Related

Swift UIImageView Firebase DispatchQueue

I am using firebase to save and load my images. I have created a new view in Xcode and am using the same code I have been using to load profile images. Yet, this is now throwing an error saying that the url string is nil. The image url data disappears after "DispatchQueue.global().async". What could be causing this and how could I track this? Very strange how this code works for other views yet for this new view it is throwing an error.
let businessProfilePicture = dictionary["profPicString"] as! String
if businessProfilePicture.count > 0 {
let url = URL(string: businessProfilePicture)
print(url)
print("printing the url here to check")
DispatchQueue.global().async {
let dataURL = try? Data(contentsOf: url!)
print(dataURL)
print("printing the data url here")
DispatchQueue.main.async {
print(dataURL)
print("Printing Data to check")
let image = UIImage(data: dataURL!)?.potter_circleo
self.businessProfilePicture.contentMode = UIView.ContentMode.scaleAspectFill
self.businessProfilePicture.image = image
}
}
Full Code
func getWorkLocation() {
let uid = Auth.auth().currentUser?.uid
var profPicURL: String = ""
Database.database().reference().child("employees").child(uid!).child("Business").observe(.value, with: { snapshot in
if snapshot.exists() {
let dictionary = snapshot.value as? NSDictionary
self.businessName.text = dictionary?["businessName"] as? String
self.businessStreet.text = dictionary?["businessStreet"] as? String
self.businessCity.text = dictionary?["businessCity"] as? String
profPicURL = dictionary?["profPicString"] as! String
// set image
if profPicURL.count > 0 {
let url = URL(string: profPicURL)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)?.potter_circle
self.businessProfilePicture.contentMode = UIView.ContentMode.scaleAspectFill
self.businessProfilePicture.image = image
}
}
} else {
let image = UIImage(named: "profile picture")?.potter_circle
self.businessProfilePicture.contentMode = UIView.ContentMode.scaleAspectFill
self.businessProfilePicture.image = image
}
} else {
self.businessName.text = ""
self.businessStreet.text = "Go to Add Work Location to send request"
self.businessCity.text = ""
self.deleteButton.isEnabled = false
}
})
}
Are you certain that the URL you create from profPicURL is being created properly?
URL(string:) can fail and return nil. If you then go on to implicitly unwrap it in Data(contentsOf: url!) you will crash.
Similarly, try? Data(contentsOf: url) can return nil. If it does, then when you implicitly unwrap it in UIImage(data: data!) you will crash.
As Jacob said in comments, you need to learn more about implicitly unwrapped optionals. To get you started, you might structure your code something like this:
if let url = URL(string: profPicURL) {
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url),
let image = UIImage(data: data)?.potter_circle
{
DispatchQueue.main.async {
self.businessProfilePicture.contentMode = UIView.ContentMode.scaleAspectFill
self.businessProfilePicture.image = image
}
} else {
// raise an an error or set self.businessProfilePicture.image to a generic image or something
}
}
} else {
// raise an an error or set self.businessProfilePicture.image to a generic image or something
}

how to set custom UIImage to tabbar item

what im trying to achieve is to have custom profile IMG on a tabbar item
First: I have tried to add the IMG to the tabbar directly
let ProfileImg: UIImageView = {
let Img = UIImageView(image: UIImage(named: "PlaseHolder"))
Img.layer.cornerRadius = 20
Img.contentMode = .scaleAspectFit
Img.clipsToBounds = true
Img.layer.masksToBounds = true
Img.translatesAutoresizingMaskIntoConstraints = false
return Img
}()
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
}
func fetchUser() {
guard let uid = Auth.auth().currentUser?.uid else {return}
let userRef = Database.database().reference(withPath: "users").child(uid)
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.value ?? "")
let value = snapshot.value as? NSDictionary
guard let profileImageUrl = value?["profileImageUrl"] as? String else {return}
guard let url = URL(string: profileImageUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
if let err = err {
print("unable to get profile image", err)
}
guard let data = data else {return}
let image = UIImage(data: data)
DispatchQueue.main.async {
self.tabBarItem.image = image?.withRenderingMode(.alwaysOriginal)
self.tabBarItem.selectedImage = image?.withRenderingMode(.alwaysOriginal)
}
}.resume()
}) { (err) in
print("fail to fetch user", err)
}
}
and this was the result :
iphone simulator image
the image is too big for a tabbar item image
but then I found another method I couldn't dead it or get it, it did add the image but I couldn't make it fit inside the tabbar + the corner radius is not working
extension UITabBarController {
func addSubviewToLastTabItem(_ imageName: UIImage) {
if let lastTabBarButton = self.tabBar.subviews.last, let tabItemImageView = lastTabBarButton.subviews.last {
if let accountTabBarItem = self.tabBar.items?.first {
accountTabBarItem.selectedImage = nil
accountTabBarItem.image = nil
}
let imgView = UIImageView()
imgView.frame = tabItemImageView.frame
imgView.layer.cornerRadius = tabItemImageView.frame.height/2
imgView.layer.masksToBounds = true
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
imgView.image = imageName
self.tabBar.subviews.last?.addSubview(imgView)
}
}
}
and this is how to call it
self.tabBarController?.addSubviewToLastTabItem(image!)
Result : iphone simulator image
please any idea to accomplish this
use this extintion to resize your image
extension UIImage {
func resize(targetSize: CGSize) -> UIImage {
return UIGraphicsImageRenderer(size:targetSize).image { _ in
self.draw(in: CGRect(origin: .zero, size: targetSize))
}
}
}
here is how to use it in dispatch method
DispatchQueue.main.async { [weak self] in
guard let data = data else {return}
self?.ProfileImg.image = UIImage(data: data)?.resize(targetSize: CGSize(width: 33, height: 33)).roundMyImage.withRenderingMode(.alwaysOriginal)
self?.tabBar.items?[0].selectedImage = self?.ProfileImg.image
self?.tabBar.items?[0].image = self?.ProfileImg.image
}
}.resume()

How to load images dynamically inside UITextView

I have a UITextView and appending TextAttachments inside of it. I am getting photos from web url. Here is the code:
image.load(url: URL(string: ("http://www.furkan.webofisin.com/upload/media/5db1b96366cd8.jpg")))
imageAttachment.image = image.image
let image1String = NSAttributedString(attachment: imageAttachment)
fullString.append(image1String)
And this is the load function as extension:
var imageUrlString: String?
func load(urlString: String) {
imageUrlString = urlString
guard let url = URL(string: urlString) else {
DispatchQueue.main.async {
let urlMessage = "Hata"
appDelegate.errorView(message: urlMessage, color: smoothGreenColor)
}
return
}
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as NSString) {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url) { (data , response, error) in
if error != nil {
DispatchQueue.main.async {
let urlMessage = "Bir hata meydana geldi."
appDelegate.errorView(message: urlMessage, color: smoothGreenColor)
}
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache ?? UIImage(named: "astast")!, forKey: urlString as NSString)
}
}.resume()
}
Images not displaying at first time after refresh page its displaying because of caching.
I checked image.image as UIImage but its always nil before loading finished.
Here is the screenshot of first opening.
At first opening image data is nil
How can I detect and reload images when image data filled.
Add a completion as call to get image initially is asynchronous unlike from local cache
func load(urlString: String,completion:#escaping(() -> () )) {
imageUrlString = urlString
guard let url = URL(string: urlString) else {
DispatchQueue.main.async {
let urlMessage = "Hata"
appDelegate.errorView(message: urlMessage, color: smoothGreenColor)
}
return
}
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as NSString) {
self.image = imageFromCache
completion()
return
}
URLSession.shared.dataTask(with: url) { (data , response, error) in
if error != nil {
DispatchQueue.main.async {
let urlMessage = "Bir hata meydana geldi."
appDelegate.errorView(message: urlMessage, color: smoothGreenColor)
}
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache ?? UIImage(named: "astast")!, forKey: urlString as NSString)
completion()
}
}.resume()
}
Call
image.load(url: URL(string: ("http://www.furkan.webofisin.com/upload/media/5db1b96366cd8.jpg"))) {
imageAttachment.image = image.image
let image1String = NSAttributedString(attachment: imageAttachment)
fullString.append(image1String)
}

How to put image to NSCache in Swift?

I make some code using swift 4 to load image from URL, but every time I add images to server, it took a lot of time to load it in colection view or table view. I want to try store it in NScache but i dont understand to do it. can anyone help me, I'm new in swift :(
#objc func loadPosts() {
let url = URL(string: "http://someURL/Url.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "phomepost=\(homepost)"
request.httpBody = body.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
if error == nil {
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
self.comments.removeAll(keepingCapacity: false)
self.images.removeAll(keepingCapacity: false)
self.collectionView?.reloadData()
guard let parseJSON = json else {
print("Error While Parsing")
return
}
guard let posts = parseJSON["posts"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
self.comments = posts.reversed()
for i in 0 ..< self.comments.count {
let path = self.comments[i]["path"] as? String
if !path!.isEmpty {
let url = NSURL(string: path!)!
let imageData = try? Data(contentsOf: url as URL)
let image = UIImage(data: imageData! as Data)!
self.images.append(image)
} else {
let image = UIImage()
self.images.append(image)
}
}
self.collectionView?.reloadData()
//print(posts.count)
} catch {
print(error)
}
}else{
print(error)
}
})
}.resume()
}
You can use something like this:
private let cache = NSCache<NSString, NSData>()
.....
func downloadImage(url: String, handler: #escaping(Data?, Error?) -> Void){
let cacheID = NSString(string: url)
if let cachedData = cache.object(forKey: cacheID) {
handler((cachedData as Data), nil)
}else{
if let url = URL(string: url) {
let session = URLSession(configuration: urlSessionConfig)
var request = URLRequest(url: url)
request.cachePolicy = .returnCacheDataElseLoad
request.httpMethod = "get"
session.dataTask(with: request) { (data, response, error) in
if let _data = data {
self.cache.setObject(_data as NSData, forKey: cacheID)
handler(_data, nil)
}else{
handler(nil, error)
}
}.resume()
} else {
// NetworkError is a custom error
handler(nil, NetworkError.invalidURL)
}
}
}
}
This will add a small animation while loading using image set.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageFromUrl(urlString: String) {
let loader1 = UIImage(named: "loaderImage1.png")
let loader2 = UIImage(named: "loaderImage2.png")
let loader3 = UIImage(named: "loaderImage3.png")
let imageArray = [loader1, loader2, loader3]
let animatedImage = UIImage.animatedImage(with: imageArray as! [UIImage], duration: 1.7)
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage{
self.image = imageFromCache
return
} else {
self.image = animatedImage
Alamofire.request(urlString, method: .get).response { (responseData) in
if let data = responseData.data {
DispatchQueue.main.async {
if let imageToCache = UIImage(data: data){
imageCache.setObject(imageToCache, forKey: urlString as AnyObject)
self.image = imageToCache
}
}
}
} //alamofire
}
}
}

How to cache images using URLSession in Swift

I would like to enhance the code below to cache images and only download them if they haven't been cached previously. I can't seem to find any good examples of how to use URLSession object to do this.
extension UIImageView {
func loadImageWithURL(_ url: URL) -> URLSessionDownloadTask {
let session = URLSession.shared
let downloadTask = session.downloadTask(with: url, completionHandler: { [weak self] url, response, error in
if error == nil, let url = url,
let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
if let strongSelf = self {
strongSelf.image = image
}
}
}
})
downloadTask.resume()
return downloadTask
}
}
Updated for Swift 4
import UIKit
let imageCache = NSCache<AnyObject, AnyObject>()
class ImageLoader: UIImageView {
var imageURL: URL?
let activityIndicator = UIActivityIndicatorView()
func loadImageWithUrl(_ url: URL) {
// setup activityIndicator...
activityIndicator.color = .darkGray
addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
imageURL = url
image = nil
activityIndicator.startAnimating()
// retrieves image if already available in cache
if let imageFromCache = imageCache.object(forKey: url as AnyObject) as? UIImage {
self.image = imageFromCache
activityIndicator.stopAnimating()
return
}
// image does not available in cache.. so retrieving it from url...
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
print(error as Any)
DispatchQueue.main.async(execute: {
self.activityIndicator.stopAnimating()
})
return
}
DispatchQueue.main.async(execute: {
if let unwrappedData = data, let imageToCache = UIImage(data: unwrappedData) {
if self.imageURL == url {
self.image = imageToCache
}
imageCache.setObject(imageToCache, forKey: url as AnyObject)
}
self.activityIndicator.stopAnimating()
})
}).resume()
}
}
Usage:
// assign ImageLoader class to your imageView class
let yourImageView: ImageLoader = {
let iv = ImageLoader()
iv.frame = CGRect(x: 10, y: 100, width: 300, height: 300)
iv.backgroundColor = UIColor(red: 0.94, green: 0.94, blue: 0.96, alpha: 1.0)
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
// unwrapped url safely...
if let strUrl = "https://picsum.photos/300/300".addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
let imgUrl = URL(string: strUrl) {
yourImageView.loadImageWithUrl(imgUrl) // call this line for getting image to yourImageView
}
One potential solution to this would be to utilize NSCache to take care of caching. Essentially what you would do is check if you already have the image locally to load from rather than fetching every time before you actually make a request.
Here's one of my implementations, however - it's a subclass rather than an extension:
class CustomImageView: UIImageView {
// MARK: - Constants
let imageCache = NSCache<NSString, AnyObject>()
// MARK: - Properties
var imageURLString: String?
func downloadImageFrom(urlString: String, imageMode: UIViewContentMode) {
guard let url = URL(string: urlString) else { return }
downloadImageFrom(url: url, imageMode: imageMode)
}
func downloadImageFrom(url: URL, imageMode: UIViewContentMode) {
contentMode = imageMode
if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) as? UIImage {
self.image = cachedImage
} else {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
let imageToCache = UIImage(data: data)
self.imageCache.setObject(imageToCache!, forKey: url.absoluteString as NSString)
self.image = imageToCache
}
}.resume()
}
}
}
Additionally, here's a useful resource:
https://www.hackingwithswift.com/example-code/system/how-to-cache-data-using-nscache
URLSession DataTask by default will cache the image automatically and you don't need to do anything on client-side as long as the cache setting on the server is normal. Images are static assets and won't change in short time, as the result, server will normally set "Cache-Control" to "public, max-age:xxxxx". URLSession default cache policy will cache the image both in memory and disk. However, it won't cache the image whose size is larger than 5% of disk size allocated for URLCache, and it doesn't do caching in background thread either.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageFromUrl(urlString: String) {
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage{
self.image = imageFromCache
return
}
Alamofire.request(urlString, method: .get).response { (responseData) in
if let data = responseData.data {
DispatchQueue.main.async {
if let imageToCache = UIImage(data: data){
imageCache.setObject(imageToCache, forKey: urlString as AnyObject)
self.image = imageToCache
}
}
}
}
}
}