Is it appropriate to send UIImage between two Uiviewcontrollers?
I'm working on assignment 4 of CS193P Spring - Smashtag.
There I have to implement Mention Table View (It's kind of additional data of tweet: mentions, hashtags, images, urls). I have to place images to appropriate cell there. For that purpose I already download it. After that If user tap on one of that images It should segue to another UIViewController where user can zoom and scroll image.
In many examples which I've seen, people send url of image and fetch it again and again (for mention controller and the same image for another one). I think It decreases perfomance. So I send UIImage object between controllers.
But is it correct?
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Storyboard.SearchSegue {
if let svc = segue.destinationViewController as? SearchResultsTableViewController {
if let cell = sender as? TextTableViewCell {
if let text = cell.hashtagLabel.text {
if text.hasPrefix("#") || text.hasPrefix("#") {
svc.searchText = text
}
}
}
} else if let svc = segue.destinationViewController as? ImageViewController {
if let cell = sender as? ImagesTableViewCell {
svc.image =
}
}
}
}
svc.image is var:
var image: UIImage? {
get {
return imageView.image
}
set {
imageView.image = newValue
imageView.sizeToFit()
scrollView?.contentSize = imageView.frame.size
}
}
and cell.imageVar
var imageVar: UIImage? {
get {
return imageField.image
}
set {
imageField.image = newValue
spinner.stopAnimating()
}
}
fetch function
private func fetchImage () {
if let u = url {
spinner?.startAnimating()
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let contentsOfURL = NSData(contentsOfURL: u)
dispatch_async(dispatch_get_main_queue()) {
if u == self.url {
if let imageData = contentsOfURL {
self.imageVar = UIImage(data: imageData)
} else {
self.spinner.stopAnimating()
}
}
}
}
}
}
That only make sense if you're using different image sizes. For instance, if you're using the image in a cell, you should use a small image, a thumbnail, to make the download faster. And then when you see the detail and want to zoom the image, you should use a bigger image (so you have to retrieve it using the url).
But if you only have one image size, it's totally correct to pass an UIImage through view controller.
You can think of caching the image. You can save the image in document directory and use its link to load in other places. One thing however you need to keep in mind is that you need to keep a track of how long you want the images to be cached this way, and a mechanism for deleting them. If you want the image only for one session you can even choose to use NSCache.
Related
I'm building a tab bar controller based application programmatically. I'm trying to figure out that how to send data from what I take to be the child of one tab bar view controller (an additional presented VC on top of viewControllers[1]), to a collection view of another one of the tab bar VCs at viewControllers[0]). I've read a bunch of entries here but none seem to be working for me or I'm not understanding them.
Essentially, within the VC in viewControllers[1], I've implemented a custom, full screen camera by implementing this code within the main TabVC.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewControllers?.firstIndex(of: viewController) == 1 {
present(cameraVC, animated: true, completion: nil)
}
return true
}
Then within that VC, I'm capturing the image and stacking a new editVC on top of that using present(editVC) to show the photo captured as well as some edit tools. I say "stacking" even though I don't know if these actually become a part of a hierarchy when presented like this? Could use some clarity on that. From that editingVC, I'm implementing the edits and saving the files to the documentsDirectory. All that works fine and the image successfully saves. But where I'm stuck is getting the path's address for the photo I've saved back to my collection view VC at viewControllers[0] so that I can populate its cells with the photo(s).
I've tried creating a delegate but I realized I can't set viewControllers[0] as a delegate for the (1st or maybe 2nd?) child of viewControllers[1]. (I'm unclear whether the code above essentially makes the cameraVC a child in an of itself of viewControllers[1].) Or can I/is it recommended?
Then I tried creating a state class containing an array of URLs (for the paths of the saved files) that both editVC could write to, and the viewControllers[0] collection view could read from. But I can't seem to get the editVC to update that array. I suspect I am inadvertently creating multiple instances of that "state". class from each of my different VCs?
I have so much code I'm not certain what to post but hopefully this is relevant:
In the state class:
class StatePaths {
var urls = [URL]()
}
In the editVC
// declaration at the beginning of the class
var statePaths: StatePaths?
// In the save func:
let imageName = UUID().uuidString
let imagePath = getDocumentsDirectory().appendingPathComponent(imageName)
if let jpegData = image.jpegData(compressionQuality: 0.8) {
try? jpegData.write(to: imagePath)
self.statePaths.urls.append(imagePath)
self.dismiss(animated: true)
}
In the collection view of viewControllers[0]
var statePaths = StatePaths()
// In the cellForItem func:
let path = statePaths[indexPath.item]
if let image = UIImage(contentsOfFile: path.path) {
cell.imageView.image = image
}
I also implemented some print statements that show the images are being saved, but that they are not populating the statePaths class. I also implemented collectionView.reloadData() on ViewWillAppear of my collection VC.
Any help whether on these approaches or any others would be greatly appreciated.
I suspect I am inadvertently creating multiple instances of that
"state". class from each of my different VCs?
Yes, "statePaths" are multiple instances and no need to use it. You need to fetch the saved images at your viewControllers[0].
Also, the more clear way is to decouple this code to a separate model or service.
For example:
struct ImageStorage {
static var documentsDirectory: String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
}
static var imagesDirectory: String {
let imagesDirectory = documentsDirectory + "/images/"
if !FileManager.default.fileExists(atPath: imagesDirectory) {
try? FileManager.default.createDirectory(atPath: imagesDirectory, withIntermediateDirectories: true, attributes: nil)
}
return imagesDirectory
}
static var images: [UIImage] {
guard let paths = try? FileManager.default.contentsOfDirectory(atPath: imagesDirectory) else {
return []
}
let images = paths.compactMap { UIImage(contentsOfFile: imagesDirectory + $0) }
return images
}
static func save(_ image: UIImage) {
let imageName = UUID().uuidString
let imagePath = imagesDirectory + imageName
let imageUrl = URL(fileURLWithPath: imagePath)
guard let jpegData = image.jpegData(compressionQuality: 0.8) else {
return
}
try? jpegData.write(to: imageUrl)
}
}
Then use it in viewController:
ImageStorage.save(image)
ImageStorage.images
Upd. To question about passing data: It depends.
In some cases you can pass data to viewController by using delegates, tabBarViewController stack, NotificationCenter etc.
But in other cases easier to use singleton for temporarily storing the data.
As I can see the link between viewControllers is not so obvious in your case. So the singleton is preferred.
As example:
class StatePaths {
static let shared = StatePaths()
private init() {}
var urls = [URL]()
}
StatePaths.shared.urls = urls
let urls = StatePaths.shared.urls
I have 2 UIImageViews connected to an array of images
I'm trying compare both once they are displayed but doesn't seems to work.
I tried using imageArray[Image Literal] and also imageArray[image1.png, image2.png, image3.png, image4.png, image5.png]
I'm not sure what im doing wrong.
im not looking for the code although it may help but what im looking is for a someone to guide me to the right direction
#IBOutlet weak var 1ImageView: UIImageView!
#IBOutlet weak var 2ImageView: UIImageView!
let imageArray = [image1.png, image2.png, image3.png, image4.png, image5.png]
func any() {
if (1ImageView != nil) && (2ImageView != nil) && isEqual(image1.png) {
print("match!")
} else if ...// more if statements
…last if statement} else {
print(“no match!”)
}
#IBAction func buttonPressed(_ sender: IUButton) {
any()
}
If this is not possible is there a way to assign an identifier to each of the images inside the array..
sorry for the extra question.
there is one answer on comparing 2 images using NSData but Im not sure how to implement it to an array.
thanks and sorry but the newbie question.
image.isEqual(image) seems to be unreliable, despite what documentation says. If you don't need to make a pixel perfect comparison, converting image to a data and comparing those would be sufficient.
let image1 = UIImage(named: "image1.png")
let image2 = UIImage(named: "image2.png")
let imageData1 = image1?.pngData()
let imageData2 = image2?.pngData()
if imageData1 == imageData2 {
print("images are the same")
} else {
print("images are different")
}
Looking for a specific image inside an array can build on that:
// array of images referencing image files within an Xcode project
// it's not the best idea to force unwrap those, but for the sake of simplicity
let imageArray = [UIImage(named: "image1.png")!,
UIImage(named: "image2.png")!,
UIImage(named: "image3.png")!,
UIImage(named: "image4.png")!,
UIImage(named: "image5.png")!]
func anySimilarImages() {
// find first image which is the same as 1ImageView's
let 1ImageViewImage: UIImage? = imageArray.first { (image) -> Bool in
return image.pngData() == 1ImageView.image?.pngData()
}
// find first image which is the same as 2ImageView's
let 1ImageViewImage: UIImage? = imageArray.first { (image) -> Bool in
return image.pngData() == 2ImageView.image?.pngData()
}
if 1ImageViewImage != nil && 2ImageViewImage != nil {
print("both images were found")
}
else if 1ImageViewImage != nil {
print("first image was found")
}
else if 2ImageViewImage != nil {
print("second image was found")
}
else {
print("no image was found")
}
}
I am using parse to retrieve my images and labels and display it on a collection view. The problem was that the collection view loads all the images and labels at once making the load time long and memory usage was high. I was thinking that I would load 10 cells each time however I was recommended to use SDWebImage to make the app lighter. However I don't know how to implement it with parse using swift. I am suspecting that I would put some code in this piece of code below
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newview", forIndexPath: indexPath) as! NewCollectionViewCell
let item = self.votes[indexPath.row]
let gesture = UITapGestureRecognizer(target: self, action: Selector("onDoubleTap:"))
gesture.numberOfTapsRequired = 2
cell.addGestureRecognizer(gesture)
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
// Display the country name
if let user = item["uploader"] as? PFUser{
item.fetchIfNeeded()
cell.userName!.text = user.username
var profileImgFile = user["profilePicture"] as! PFFile
cell.profileImageView.file = profileImgFile
cell.profileImageView.loadInBackground { image, error in
if error == nil {
cell.profileImageView.image = image
}
}
var sexInt = user["sex"] as! Int
var sex: NSString!
if sexInt == 0 {
sex = "M"
}else if sexInt == 1{
sex = "F"
}
var height = user["height"] as! Int
cell.heightSexLabel.text = "\(sex) \(height)cm"
}
if let votesValue = item["votes"] as? Int
{
cell.votesLabel?.text = "\(votesValue)"
}
// Fetch final flag image - if it exists
if let value = item["imageFile"] as? PFFile {
println("Value \(value)")
cell.postsImageView.file = value
cell.postsImageView.loadInBackground({ (image: UIImage?, error: NSError?) -> Void in
if error != nil {
cell.postsImageView.image = image
}
})
}
return cell
}
I have implemented SDWebImage using Pods and have imported through the Bridging Header. Is there anyone who knows how to implement SDWebImage with parse using Swift?
You should rethink your approach -
I believe you are using collectionViewDelegate method - collectionView(_:cellForItemAtIndexPath:)
this fires every time the collection view needs a view to handle.
In there you can access the cell imageView and set its image (For Example)-
cell.imageView.sd_setImageWithURL(url, placeholderImage:placeHolderImage, completed: { (image, error, cacheType, url) -> Void in })
And if you wish to fade in the image nicely, you could -
cell.imageView.sd_setImageWithURL(url, placeholderImage:placeHolderImage, completed: { (image, error, cacheType, url) -> Void in
if (cacheType == SDImageCacheType.None && image != nil) {
imageView.alpha = 0;
UIView.animateWithDuration(0.0, animations: { () -> Void in
imageView.alpha = 1
})
} else {
imageView.alpha = 1;
}
})
EDIT
I see the you use Parse, so you don't need SDWebImage, you need to use Parse - PFImageView, It will handle your background fetch for the image when it loads. You will need to save reference to your PFObject, but I believe you already do that.
For example (inside your cellForItemAtIndexPath)-
imageView.image = [UIImage imageNamed:#"..."]; // placeholder image
imageView.file = (PFFile *)someObject[#"picture"]; // remote image
[imageView loadInBackground];
How many objects are displaying in the collection view?
Since you mentioned SDWebImage, are you downloading the images in the background as well?
If you want to load the images as the user scrolls, have a look at the documentation for SDWebImage. The first use case describes how to display images in table view cells withouth blocking the main thread. The implementation for collection view cells should be similar.
I have a map that allows me to add pins (annotations) to it. When I click on a pin, the app goes to another view controller and shows images downloaded from online. I "save" these images to an array but later I cannot access them. For example, when I tap "Back" to go to the previous view controller and tap on the same pin from before, instead of showing the images I originally downloaded and saved, new images are downloaded from online. Essentially, the images in the array are replaced. How can I save the images and retrieve them? Sorry for long amounts of code, I shortened as much as I could.
This is my class for the images:
class Image {
var image: UIImage
init(image: UIImage) {
self.image = image
}
}
This is my class for the pins. Notice, an array of type Image from the Image class is in here:
class Pin: Hashable {
var hashValue: Int {
get {
return "\(latitude.hashValue),\(longitude.hashValue)".hashValue
}
}
let latitude: Double
let longitude: Double
var images = Array<Image>()
init(latitude: Double, longitude: Double)
{
self.latitude = latitude
self.longitude = longitude
}
}
// must be declared in the global scope! and not just in the class scope
func ==(lhs: Pin, rhs: Pin) -> Bool
{
return lhs.hashValue == rhs.hashValue
}
I add pins to a Set like so (no duplicates of pins allowed). Also, the selected pin is sent to the secondViewController:
class MapViewController: UIViewController, MKMapViewDelegate, UIGestureRecognizerDelegate {
var pins = Set<Pin>()
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
// Add pin to set
let selectedCoordinatePoint = Pin(latitude: latFromPin, longitude: lonFromPin)
var select = "\(latFromPin.hashValue),\(lonFromPin.hashValue)".hashValue
pins.insert(selectedCoordinatePoint)
//Goto to next view controller and show data depending on the pin selected
for pin in pins {
if pin.hashValue == select.hashValue {
dispatch_async(dispatch_get_main_queue()) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = self.storyboard!.instantiateViewControllerWithIdentifier("CollectionViewControllerID") as! CollectionViewController
secondViewController.pin = pin
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
}
}
}
I download images from online and append to an array on this secondViewController (I shortened the code here):
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, MKMapViewDelegate {
var pin: Pin!
override func viewWillAppear(animated: Bool) {
if let photosArray = photosDictionary["photo"] as? [[String:AnyObject]] {
// println("photosArray = \(photosArray )")
var count = 0
for photo in photosArray {
// 6 - Grab 21 random images
if count <= 20 {
// Grabs 1 image
let randomPhotoIndex = Int(arc4random_uniform(UInt32(photosArray.count)))
let photoDictionary = photosArray[randomPhotoIndex] as [String:AnyObject]
// 7 - Get the image url
let imageUrlString = photoDictionary["url_m"] as? String
let imageURL = NSURL(string: imageUrlString!)
// 8 - If an image exists at the url, append to array
let imageData = NSData(contentsOfURL: imageURL!)
let finalImage = UIImage(data: imageData!)
var image = Image(image: finalImage!)
self.pin.images.append(image)
count += 1
println(self.pin.images.count)
}
}
}
}
}
I'm not I understand what your asking exactly...you are making network calls to populate your image array. If you just want to pass the image array from VC to VC, you can use prepareForSegue to pass the value.
If you want to pull these down and then store them locally you need to read up on persistence. For example you can use something like Core Data, Parse, Realm...I personally use Parse because of it's ease. This is a good tutorial along the lines of what you're doing I think. It uses Parse to store the image data: http://www.appcoda.com/instagram-app-parse-swift/
[UPDATE]
I have added actual code snippet in order to make my question clear.
Say we want to store uiimages into an array, which are fetched from the internet.
I have this code snippet:
// Somewhere in a loop
{
var story = Story()
story.imgUrl = "http:\(imgUrl)"
/// Donwload image, and replace in the top
if let imgUrl = story.imgUrl {
if let url = NSURL(string: imgUrl) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if let data = data {
story.image = UIImage(data: data)
var i = 0
for a in self.Stories {
print("iv image \(i++) is \(a.image)")
}
print("Received image for story as \(story.image) into story \(story)")
// Should one set the image view?
if let _ = self.imageView {
if let indexPath = self.tableView?.indexPathForSelectedRow {
if stories.count == indexPath.section { // is that the currently selected section?
self.imageView?.image = story.image
self.imageView?.hidden = false
print("Set imageView withstory \(story.tag.string)")
}
}
}
}
}
}
}
stories.append(story)
}
/// Switch data source
self.Stories = stories
This doesn't store the image property value into the destination array.
Though the image is ok in the block, if I iterate through the destination array, my image is nil
image: Optional(<UIImage: 0x7ff14b6e4b20> size {100, 67} orientation 0 scale 1.000000))
iv image 0 is nil
How to achieve above functionality?
[INITIAL QUESTION]
Say we want to store element i.e UIImages which i have fetched from the internet. I have this code snippet:
var array = []
let imageView = UIImageView(image:nil)
array.append(imageView)
// and later or, in an synch block
imageView.image = image
This doesn't store the image property value in the array.
How could I do that?
Gotcha!
The point is that Story was defined as struct. And structs are passed by values unlike classes.
To make the code working, I just changed from struct Story {} to class Story {}, and voila!