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()
Related
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)
}
This is my first time using NSCache for a table view. For cellForRow I call an NSCache loading an image. The image sometimes is the wrong image. What can I do to fix this? If the cache does not contain it, I replace the photo with "randomguy".
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyMessagesTableViewCell", for: indexPath) as! MyMessagesTableViewCell
if let cachedImage = cache.object(forKey: urlArray[indexPath.row] as NSString) as? UIImage {
cell.proPicImageView.image = cachedImage
cell.proPicImageView.layer.cornerRadius =
cell.proPicImageView.frame.size.height / 2
cell.proPicImageView.layer.masksToBounds = true
cell.proPicImageView.layer.borderWidth = 0
cell.usernameLabel.text = usernameArray[indexPath.row]
cell.messageLabel.text = messageArray[indexPath.row]
cell.messageLabel.textColor = colorArray[indexPath.row]
cell.messageLabel.font = fontArray[indexPath.row]
cell.timeLabel.text = timeArray[indexPath.row]
} else {
cell.proPicImageView.image = UIImage(named: "randomguy")
cell.proPicImageView.layer.cornerRadius =
cell.proPicImageView.frame.size.height / 2
cell.proPicImageView.layer.masksToBounds = true
cell.proPicImageView.layer.borderWidth = 0
cell.usernameLabel.text = usernameArray[indexPath.row]
cell.messageLabel.text = messageArray[indexPath.row]
cell.messageLabel.textColor = colorArray[indexPath.row]
cell.messageLabel.font = fontArray[indexPath.row]
cell.timeLabel.text = timeArray[indexPath.row]
}
return cell
}
In view Did Load:
let cache = NSCache()
func photoQuery () {
for username in self.usernameArray {
let photoQuery = PFQuery(className: "UserPhoto")
photoQuery.whereKey("username", equalTo: username)
photoQuery.findObjectsInBackground(block: { (objects:
[PFObject]?,error: Error?) in
if let objects = objects {
for object in objects {
if error == nil {
let userImageFile = object["photo"] as? PFFileObject
let urlString = userImageFile?.url as! String
if let url = URL(string: urlString) {
let data = try? Data(contentsOf: url)
if let imageData = data {
self.messageImageArray.append(UIImage(data:imageData)!)
self.cache.setObject(UIImage(data:imageData)!, forKey: urlString as
NSString)
self.urlArray.append(urlString as NSString)
print(self.messageImageArray)
}
}
}
}
}
})
}
}
This can be one way where you can create your custom image view and have a function inside this that will take care of downloading and assigning image to the proper cell
let cache = NSCache<NSString, UIImage>()
class CustomImageView: UIImageView {
var imageURLString: String?
func startImageDownloadOperation(url: URL) {
imageURLString = url.absoluteString
if let cachedVersion = cache.object(forKey: url.absoluteString as NSString) {
self.image = cachedVersion
} else {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil,
let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
DispatchQueue.main.async {
self.image = UIImage(named: "default")
}
return
}
DispatchQueue.main.async {
if let data = data {
let imageToCache = UIImage(data: data)
if url.absoluteString == self.imageURLString {
self.image = imageToCache
cache.setObject(imageToCache!, forKey: url.absoluteString as NSString)
}
}
}
}
task.resume()
}
}
}
I am trying to call data from firebase that changes the image view to be that of what is stored in the database. when I call my code, I see
let image: UIImageView = {
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("users").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else {
return }
let user = MyUser(dictionary: dictionary as [String : AnyObject])
let profileImageView = UIImageView()
profileImageView.translatesAutoresizingMaskIntoConstraints = false
profileImageView.contentMode = .scaleAspectFill
profileImageView.layer.cornerRadius = 30
profileImageView.clipsToBounds = true
profileImageView.image = UIImage(named: "app")
profileImageView.image=#imageLiteral(resourceName: "user")
profileImageView.loadImageUsingCacheWithUrlString(user.profileImageUrl!)
return profileImageView
}, withCancel: { (err) in
print("attempting to load information")
})
// return profileImageView
}()
where the commented out return function is, I am getting the error Use of unresolved identifier 'profileImageView'; did you mean 'provideImageData'?
why? I would assume that since the return is within the closures, it would know what profileImageView is.
Separate the image view and the real time database listener:
let imageView: UIImageView {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 30
imageView.clipsToBounds = true
return imageView
}
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("users").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else {
return }
let user = MyUser(dictionary: dictionary as [String : AnyObject])
// Set the image view's image
imageView.image = // ...
}, withCancel: { (err) in
print("attempting to load information")
})
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
}
}
}
}
}
}
the code I currently have gets a profile picture from firebase and displays it on the map as the marker icon.
self.ref.child("users").child(location.key as! String).child("userPhoto").observeSingleEventOfType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
if(snapshot.exists()) {
let profileUrl = snapshot.value as? String
let url = NSURL(string: profileUrl!)
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) in
if error != nil {
print(error)
return
}
dispatch_async(dispatch_get_main_queue(), {
if let downloadedImage = UIImage(data: data!) {
marker.icon = downloadedImage
}
})
}).resume()
}
})
Unfortunately, this code just plops a really big image on the map which i dont want. i want a circular image with a border/background that makes it look like a pin/marker. Like this:
Ideal Deign of The Marker Icon
Would anyone know how to do this?
Any help would be appreciated.
Thanks!
P.S: I am new to swift and would really appreciate if you could show me some code with whatever solution you provide. Thanks!
func changeImageDimensions(image: UIImage, newWidth: CGFloat, newHeight: CGFloat) -> UIImage {
let widthRat = newWidth/image!.size.width
let heightRat = newHeight/image!.size.height
var newSize: CGSize = CGSize()
if(widthRat > heightRat) {
newSize = CGSizeMake(image!.size.width * heightRat, image!.size.height * heightRat)
} else {
newSize = CGSizeMake(image!.size.width * widthRat, image!.size.height * widthRat)
}
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
let frameRect = CGRectMake(0, 0, newSize.width, newSize.height)
image.drawInRect(frameRect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
self.ref.child("users").child(location.key as! String).child("userPhoto").observeSingleEventOfType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
if(snapshot.exists()) {
let profileUrl = snapshot.value as? String
let url = NSURL(string: profileUrl!)
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) in
if error != nil {
print(error)
return
}
dispatch_async(dispatch_get_main_queue(), {
if let downloadedImage = UIImage(data: data!) {
marker.icon = changeImageDimensions (downloadedImage!,newWidth:CGFloat(150),newHeight:CGFloat(150))
}
})
}).resume()
}
})