Fullscreen view for a collectionview - ios

This is more of an approach question. Suppose I have a collection view where each cell is occupying the entire screen, which have images in them and some other data(eg: title, info etc). What I want is for the user to tap on the cell and the image to go to fullscreen mode. I am able to achieve this by initializing a separate view and scaling the image to fit the screen with this code.
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = UIScreen.main.bounds
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
and then dismissing it by removing the view like so:
self.navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
sender.view?.removeFromSuperview()
}
Q1. Is there a way I can manipulate all the collectionview cells to transform into the fullscreen view on tap so i can use the default swiping action of the collection view to scroll through the images horizontally ?
Q2. If not, I use this library INSPhotoGallery! to add the effect on tap, which gives me the desired effect but due to heavy loading of the images from the PHAsset library my app crashes.
This is how i initialized my phassets to pass into this library:
lazy var photos: [INSPhotoViewable] = {
var allPhotos: [INSPhoto] = Array()
fetchResult.enumerateObjects({ (asset, index, stop) in
let image = self.requestImageForPHAsset(asset: asset)
allPhotos.append(INSPhoto(image: image, thumbnailImage: nil))
})
return allPhotos
}()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
let cell = collectionView.cellForItem(at: indexPath) as! DetailedCollectionViewCell
let currentPhoto = photos[indexPath.row]
let galleryPreview = INSPhotosViewController(photos: photos, initialPhoto: currentPhoto, referenceView: cell)
galleryPreview.overlayView.photosViewController?.singleTapGestureRecognizer.isEnabled = false
galleryPreview.referenceViewForPhotoWhenDismissingHandler = { [weak self] photo in
if let index = self?.photos.index(where: {$0 === photo}) {
let indexPath = IndexPath(item: index, section: 0)
return collectionView.cellForItem(at: indexPath) as? DetailedCollectionViewCell
}
return nil
}
present(galleryPreview, animated: true, completion: nil)
}
Question being how can I prevent my app from crashing. I know this has something to do with caching the images and loading asynchronously. Do you know of a library that integrates well with PHAssets if nothing else? Thanks!

You also can use page view controller to achieve the same functionality. It's valuable if you have less then 5 cells.
You can display image in a full screen modally. Just create separate view controller and implement animators(transition delegates) for this modal screen. It will be more difficult but also more proper way. The AppStore working in the same way

Related

Data From Label in CollectionViewCell Sometimes Refreshes on Reload other times it Doesn't

First let me say this seems to be a common question on SO and I've read through every post I could find from Swift to Obj-C. I tried a bunch of different things over the last 9 hrs but my problem still exists.
I have a vc (vc1) with a collectionView in it. Inside the collectionView I have a custom cell with a label and an imageView inside of it. Inside cellForItem I have a property that is also inside the the custom cell and when the property gets set from datasource[indePath.item] there is a property observer inside the cell that sets data for the label and imageView.
There is a button in vc1 that pushes on vc2, if a user chooses something from vc2 it gets passed back to vc1 via a delegate. vc2 gets popped.
The correct data always gets passed back (I checked multiple times in the debugger).
The problem is if vc1 has an existing cell in it, when the new data is added to the data source, after I reload the collectionView, the label data from that first cell now shows on the label in new cell and the data from the new cell now shows on the label from old cell.
I've tried everything from prepareToReuse to removing the label but for some reason only the cell's label data gets confused. The odd thing is sometimes the label updates correctly and other times it doesn't? The imageView ALWAYS shows the correct image and I never have any problems even when the label data is incorrect. The 2 model objects that are inside the datasource are always in their correct index position with the correct information.
What could be the problem?
vc1: UIViewController, CollectionViewDataSource & Delegate {
var datasource = [MyModel]() // has 1 item in it from viewDidLoad
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell
cell.priceLabel.text = ""
cell.cleanUpElements()
cell.myModel = dataSource[indexPath.item]
return cell
}
// delegate method from vc2
func appendNewDataFromVC2(myModel: MyModel) {
// show spinner
datasource.append(myModel) // now has 2 items in it
// now that new data is added I have to make a dip to fb for some additional information
firebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let dict = snapshot.value as? [String: Any] else { }
for myModel in self.datasource {
myModel.someValue = dict["someValue"] as? String
}
// I added the gcd timer just to give the loop time to finish just to see if it made a difference
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
self.datasource.sort { return $0.postDate > $1.postDate } // Even though this sorts correctly I also tried commenting this out but no difference
self.collectionView.reloadData()
// I also tried to update the layout
self.collectionView.layoutIfNeeded()
// remove spinner
}
})
}
}
CustomCell Below. This is a much more simplified version of what's inside the myModel property observer. The data that shows in the label is dependent on other data and there are a few conditionals that determine it. Adding all of that inside cellForItem would create a bunch of code that's why I didn't update the data it in there (or add it here) and choose to do it inside the cell instead. But as I said earlier, when I check the data it is always 100% correct. The property observer always works correctly.
CustomCell: UICollectionViewCell {
let imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let priceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var someBoolProperty = false
var myModel: MyModel? {
didSet {
someBoolProperty = true
// I read an answer that said try to update the label on the main thread but no difference. I tried with and without the DispatchQueue
DispatchQueue.main.async { [weak self] in
self?.priceLabel.text = myModel.price!
self?.priceLabel.layoutIfNeeded() // tried with and without this
}
let url = URL(string: myModel.urlStr!)
imageView.sd_setImage(with: url!, placeholderImage: UIImage(named: "placeholder"))
// set imageView and priceLabel anchors
addSubview(imageView)
addSubview(priceLabel)
self.layoutIfNeeded() // tried with and without this
}
}
override func prepareForReuse() {
super.prepareForReuse()
// even though Apple recommends not to clean up ui elements in here, I still tried it to no success
priceLabel.text = ""
priceLabel.layoutIfNeeded() // tried with and without this
self.layoutIfNeeded() // tried with and without this
// I also tried removing the label with and without the 3 lines above
for view in self.subviews {
if view.isKind(of: UILabel.self) {
view.removeFromSuperview()
}
}
}
func cleanUpElements() {
priceLabel.text = ""
imageView.image = nil
}
}
I added 1 breakpoint for everywhere I added priceLabel.text = "" (3 total) and once the collectionView reloads the break points always get hit 6 times (3 times for the 2 objects in the datasource).The 1st time in prepareForReuse, the 2nd time in cellForItem, and the 3rd time in cleanUpElements()
Turns out I had to reset a property inside the cell. Even though the cells were being reused and the priceLabel.text was getting cleared, the property was still maintaining it's old bool value. Once I reset it via cellForItem the problem went away.
10 hrs for that, smh
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell
cell.someBoolProperty = false
cell.priceLabel.text = ""
cell.cleanUpElements()
cell.myModel = dataSource[indexPath.item]
return cell
}

cell reuse another image in collectionview inside tableview

Currently,
I have a feed for the user and
it uses uitableview and
there is a uicollectionview inside tableview for multi-images.
'Like' or 'Comment' functions are working well
but some issues happen when user taps 'Like'.
I do not want to show several changes,
but when user taps 'Like', I need to reload a cell and it shows another picture for a short time(bcs of reuse) and back to original image.
I tried to use the function, prepareForReuse().
However, I am not sure how to maintain the same image currently on the screen when they are reloading. Any idea u have?
For ur more information,
let me show my tableview's part of 'CellForItemAt' and collectionview's same method.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "storyCell", for: indexPath) as! StoryPhotoCollectionViewCell
let imageURL = photoList[indexPath.row]
let url = URL(string: imageURL)
DispatchQueue.main.async {
cell.imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "imageplaceholder"), options: nil, progressBlock: nil, completionHandler: nil)
}
cell.imageView.kf.indicatorType = .activity
return cell
}
The collectionView's datasource is photoList array, so in the tableview, I have this code.
DispatchQueue.main.async {
cell.photoList = (story.imageArray?.components(separatedBy: ",").sorted())!
}
The issue should be due to delay in asynchronous download of the image,
I believe your code would be like this
cell.image = downloadFromURL(SOME_URL)
Add this single line code before assigning the image
cell.image = nil; // or some placeholder image
cell.image = downloadFromURL(SOME_URL)
If possible please provide more info
If I understand well your question, you need:
When the status change, you change the image so, this image can be stored as part of the app or external. If it is the same image, you need to keep it cached.
You start a timer like:
let countTimeBeforeChangeMyImageBack = DispatchTime.now() + .seconds(waitTimeBeforeStartCounting)
DispatchQueue.main.asyncAfter(deadline: deadlineTime, execute: {
self.YourMethodToChangeYourImageBack
})
At your method YourMethodToChangeYourImageBack you set the original image back.
Here, remember you could need to join back to the main thread before updating the UI:
// Re join the main queue after the callback
DispatchQueue.main.sync() {
// Your code to update the image
}
Hope it Helps.

Activity Indicator Not Disabling for CollectionViewCell

I have Collection view forming of 3 cells. I am displaying images on them, however, as fetching the image data from the server takes a few seconds, I use nsuserdefaults and cache.
let imagesFromUserDefaults
var dataIsFetched = false
viewDidLoad() {
let imagesFromUserDefaults = // Data from user defaults
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("ACollectionViewCell", forIndexPath: indexPath) as! ACollectionViewCell
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
activityView.hidesWhenStopped = true
if dataIsFetched == false {
activityView.center = CGPointMake( cell.contentView.frame.size.width / 2, cell.contentView.frame.size.height / 2)
cell.contentView.addSubview(activityView)
activityView.startAnimating()
cell.img = // set image - works
} else {
// Data is fetched from the Server
activityView.stopAnimating()
cell.img = // set image - works
}
func fetchDataFromServer() {
// onSuccessResponse: Store into Array
dataIsFetched = true
collectionView.reloadData()
}
However, after the server data is loaded, even though the images change, the activity indicator doesn't go away. So if cells have the same indexPath.row (as proved by images as well), why doesn't it happen with activity indicator?
Also, is it more logical to place this behaviour to CollectionViewCell class? What is the proper way of handling
Edit:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if initialLoad {
count = self.defaults.count
} else {
count = self.fetched[0].count
}
print("number of cells \(count)")
if count < 4 {
return count
}
return 0
}
You need to set
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
activityView.hidesWhenStopped = true
Also you can set activityView.center = cell.center for a cleaner code.
You know, collect view cells are reusable, the 3 cells displayed in the collection view may share the same cell instance. So every time you call the cellForItemAtIndexPath method, you may get the same cell instance, and you repeatedly add an active indicator to the cell but never remove it. That's not good. Imagine what will you to indicate if an image is fetched with YES/NO, that'll be easy, if the image is fetched, you display YES in the cell, otherwise NO. So the indicator is here to do the same thing, the indicator's YES is animating and NO is not animating, don't get tricked because it's spinning, it's not for tracking the whole fetching process. All you need to do is to add the indicator in the prototype cell, and maintain the states of you images, fetched or not. And update the indicator base on the state:
var isImageFetched = yourImagesStates[indexPath.row];
if isImageFetched == true {
cell.indicator.stopAnimating()
cell.img = image
} else {
cell.indicator.startAnimating()
}
hope it'll help.

SWIFT 2 - UICollectionView - slow scrolling

I have setup a uicollectionview in my project that get data from a JSON file. Everything works good however, the scrolling is very slow and when the view is scrolling the coming cell, for few moments shows the content of the cell before.
I have tried using dispatch_async but it still very slow and jumpy.
any Idea what am I doing wrong?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let videoCell = collectionView.dequeueReusableCellWithReuseIdentifier("VideoCell", forIndexPath: indexPath) as UICollectionViewCell
let communityViewController = storyboard?.instantiateViewControllerWithIdentifier("community_id")
videoCell.frame.size.width = (communityViewController?.view.frame.size.width)!
videoCell.center.x = (communityViewController?.view.center.x)!
videoCell.layer.borderColor = UIColor.lightGrayColor().CGColor
videoCell.layer.borderWidth = 2
let fileURL = NSURL(string:self.UserVideosInfo[indexPath.row][2])
let asset = AVAsset(URL: fileURL!)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
//self.showIndicator()
let NameLabelString = self.UserVideosInfo[indexPath.row][0]
let CommentLabelString = self.UserVideosInfo[indexPath.row][1]
let DateLabelString = self.UserVideosInfo[indexPath.row][3]
let buttonPlayUserVideo = videoCell.viewWithTag(1) as! UIButton
let nameLabel = videoCell.viewWithTag(2) as! UILabel
let commentUserVideo = videoCell.viewWithTag(3) as! UILabel
let dateUserVideo = videoCell.viewWithTag(4) as! UILabel
let thumbUserVideo = videoCell.viewWithTag(5) as! UIImageView
let deleteUserVideo = videoCell.viewWithTag(6) as! UIButton
buttonPlayUserVideo.layer.setValue(indexPath.row, forKey: "indexPlayBtn")
deleteUserVideo.layer.setValue(indexPath.row, forKey: "indexDeleteBtn")
dispatch_async(dispatch_get_main_queue()) {
nameLabel.text = NameLabelString
commentUserVideo.text = CommentLabelString
dateUserVideo.text = DateLabelString
self.shadowText(nameLabel)
self.shadowText(commentUserVideo)
self.shadowText(dateUserVideo)
if let cgImage = try? assetImgGenerate.copyCGImageAtTime(time, actualTime: nil) {
thumbUserVideo.image = UIImage(CGImage: cgImage)
}
}
}
//THIS IS VERY IMPORTANT
videoCell.layer.shouldRasterize = true
videoCell.layer.rasterizationScale = UIScreen.mainScreen().scale
return videoCell
}
At first - you are working with UI objects from global queue and seems like without any purpose. That is forbidden - or behavior will be undefined.
Secondary, the mostly heavy operation is creation of thumbnail which you perform on main queue.
Consider using of the AVAssetImageGenerator's method
public func generateCGImagesAsynchronouslyForTimes(requestedTimes: [NSValue], completionHandler handler: AVAssetImageGeneratorCompletionHandler)
instead of your own asyncs.
At third, viewWithTag is pretty heavy operation causing enumeration on subviews. Consider to declare properties in the cell for views which you need.
UPD: to declare properties in a cell, create subclass of UICollectionViewCell with appropriate properties as IBOutlets. Then, in your view controller viewDidLoad implementation, call
collecionView.registerClass(<YourCellSubclass>.dynamicType, forCellWithReuseIdentifier:"VideoCell")
Or, if your collection view cell is configured in the Storyboard, specify the class of the cell and connect its subviews to class' outlets directly in the cell's settings window in Interface Builder.
At fourth, your cells are being reused by a collection view. Each time your cell is going out of visible area, it is removed from collection view and is put to reuse queue. When you scroll back to the cell, your view controller is asked again to provide a cell. And you're fetching the thumbnail for the video again for each newly appeared cell. Consider caching of already fetched thumbnails by storing them in some array by collectionView's indexPath.item index.

UILongPressureRecognizer with Image view

Good morning everyone,
I am a newbie Swift developer and I am facing the following problem implementing an exercise I am dealing with.
I have a collection view with collection cells displaying images I have imported in XCODE; when I tap with the finger on the screen I would like to replace the image currently being display with another one that i have also imported, and animate this replacement.
I am implementing the UITapLongPressureRecognizer method but i am getting confused on which state to implement for the recognizer, just to replace the first image view with the one I want to be shown when I tap the screen to scroll up-down.
As you can see from the code below the two recognizer I think should be more appropriate to be implemented are the "Begin" and "Ended".
My problem is that when the state .Begin starts I don't know how to animate the replacement of one image view with another and so when the state is .Ended I don't know how to replace the second image with the first one and animate the replacement (I want it to be like for example a Modal segue with "Cross Dissolve" transition).
Thank you in advance for your kindness and patience.
class MainViewController: UICollectionViewController {
var fashionArray = [Fashion]()
private var selectedIndexPath: NSIndexPath?
override func viewDidLoad() {
super.viewDidLoad()
selectedIndexPath = NSIndexPath()
//Register the cell
let collectionViewCellNIB = UINib(nibName: "CollectionViewCell", bundle: nil)
self.collectionView!.registerNib(collectionViewCellNIB, forCellWithReuseIdentifier: reuseIdentifier)
//Configure the size of the image according to the device size
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let bounds = UIScreen.mainScreen().bounds
let width = bounds.size.width
let height = bounds.size.height
layout.itemSize = CGSize(width: width, height: height)
let longPressRecogn = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
collectionView!.addGestureRecognizer(longPressRecogn)
}
func handleLongPress(recognizer: UILongPressGestureRecognizer){
var cell: CollectionViewCell!
let location = recognizer.locationInView(collectionView)
let indexPath = collectionView!.indexPathForItemAtPoint(location)
if let indexPath = indexPath {
cell = collectionView!.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
}
switch recognizer.state {
case .Began:
cell.screenTapped = false
case .Ended:
cell.screenTapped = false
default:
println("")
}
}
First of all, I suggest you to use UITapGestureRecognizer instead of long press. Because, as far as I understand, you only tap once instead of pressing for a time.
let tapRecognizer = UITapGestureRecognizer(target: self, action:Selector("tapped:"))
collectionView.addGestureRecognizer(tapRecognizer)
And when the user tapped, you can use UIView animations to change the image. You can check the Example II from the following link to get insight about animations.
http://www.appcoda.com/view-animation-in-swift/

Resources