I have not seen this topic posted anywhere else - I have built an iMessage app that uses a collecitonview to hold a bunch of MSStickerViews. These stickers are animated.
My problem is that while the app itself appears to load when you first open, there is a noticeable delay before one is able to touch a sticker/interact with the app, and before the MSStickers begin animating. I am not sure whether this is a matter of the order of functions being called but I can't find a way to improve/fix this.
My MSStickers are added to collection view cells here:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "stickerCell", for: indexPath as IndexPath) as! StickerCollectionViewCell
and in a separate Cell class I then add the sticker view and set it to .startAnimating().
The delay is about 5-6 seconds. How can I reduce this? Is this possible?
Animation/sticker code:
for (animal, atlas) in animalsAnim {
animalsAnim[animal] = getImagesForAnimals(animal: animal, allSticks: allStickers)
addSticker(images: animalsAnim[animal]!, name: animal)
}
func addSticker(images: [UIImage], name: String)
{
let sticker: MSSticker
do {
try sticker=MSSticker(images: images, format: .apng, frameDelay: 0.95/14.0, numberOfLoops: 0, localizedDescription: name)
}catch MSStickerAnimationInputError.InvalidDimensions {
fatalError("ERROR: Dimens")
}catch MSStickerAnimationInputError.InvalidStickerFileSize {
fatalError("ERROR: Size")
} catch { fatalError("other error:\(error)") }
var stickerSize = CGSize()
if (UIDevice.current.iPhone5orBefore)
{
stickerSize = CGSize(width: view.bounds.width*0.38, height: view.bounds.width*0.38)
}
else {
stickerSize = CGSize(width: view.bounds.width*0.4, height: view.bounds.width*0.4)
}
let stickerView = InstrumentedStickerView(frame: CGRect(origin: CGPoint(x: 0,y :0), size: stickerSize))
stickerView.sticker = sticker
stickerView.delegate = self
stickerPack.append(stickerView)
}
The sticker view is then added and begun animating in a separate cell class.
Related
When a user clicks on a collection view item, I want to change the UILabelView text property:
// This is another viewController not the one containing the label
// Handle collectionViewItem selection
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("INSIDE TableViewCell2.collectionView 3")
TabsBarController.sharedInstance.testTitle = "UILabelText"
print("didSelectItem\(indexPath)")
}
Once it's set, I try to update it here:
class TabsBarController: UIViewController {
static let sharedInstance = TabsBarController()
var movieTitle: UILabel? = UILabel(frame: CGRect(x: 0, y: 0, width: 300.00, height: 30.00));
var testTitle: String? = nil {
didSet {
print("testTitle.didSet: ",testTitle!) // logs the correct text
movieTitle?.text = testTitle
print(" movieTitle?.text : ", movieTitle?.text ) // logs the correct text
}
}
}
The problem here is that even though movieTitle?.text, in the UI, the movieTitle UILabel doesn't change.
I've read many answers to similar question, and all of them point to using the main thread, so I added this:
class TabsBarController: UIViewController {
static let sharedInstance = TabsBarController()
var movieTitle: UILabel? = UILabel(frame: CGRect(x: 0, y: 0, width: 300.00, height: 30.00));
var testTitle: String? = nil {
didSet {
// I added this but still nothing changed.
DispatchQueue.main.async {
// Run UI Updates
print("testTitle.didSet: ",testTitle!) // logs the correct text
movieTitle?.text = testTitle
print(" movieTitle?.text : ", movieTitle?.text ) // logs the correct text
}
}
}
}
But, still the UI doesn't get updated. Any idea why is this happening and how to solve it?
NOTE: This is the hierarchy :
The hierarchy is like this TabsBarViewController-> MoviesViewController -> UITableView->UitableViewCell->CollectionView
Based on the project hierarchy, I had to follow the instructions here in order to be able to access the UITabsBarController testTitle property from inside the tableViewCell.
Just one caveat, I had to do this casting:
// Handle collectionViewItem selection
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("INSIDE TableViewCell2.collectionView 3")
if let vc2 = self.viewController?.parent as? TabsBarController {
vc2.testTitle = "THIS WILL DEFINITELY ABSOLUTELY WORK I DONT CARE"
}
print("didSelectItem\(indexPath)")
}
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
In this function I made switch on incoming data type and I invoke function that add subview to my cell. At this moment I haven't data and I inserted stubs.
Method doesn't work, result is empty space after textview( haven't picture) ,and I dont know why, I add subview to my cell, in theory it must work. Now I don't use switch. I have cell height 350 , label height 50 and textview height 50 so for image/video I keep 250.
Please help me with advice.
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionNews",for: indexPath) as! NewsCell
cell.personImage.image = UIImage(named: "welder.png")
cell.textView.layer.cornerRadius = 5
cell.addSubview(addImage(image : UIImage(named : "error.png")!, for: cell, at : indexPath ))
//switch (contentArray[indexPath.row]){
//case let image where image is String :
//cell.addSubview(addImage(image : UIImage(named : "error.png")!, for: cell, at : indexPath ))
//case let video where video is String :
//break
//default:
//break
// }
return cell
}
func addImage(for cell : NewsCell, at index : IndexPath)->UIImageView{
let testImg = UIImage(named: "error.png")
let imageForCell = UIImageView(image: testImg)
imageForCell.autoresizingMask = [UIViewAutoresizing.flexibleWidth , UIViewAutoresizing.flexibleHeight]
imageForCell.frame = CGRect(x: cell.textView.bounds.minX, y: cell.textView.bounds.maxY + 5, width: imageForCell.frame.origin.x, height: imageForCell.frame.origin.y)
imageForCell.layer.cornerRadius = 5
imageForCell.layer.masksToBounds = true
return imageForCell
}
func addVideo () {
}
First: I see that function addImage is not declaring a UIImage as a parameter
Second: You're setting frame property for imageForCell wrong. You should do something like: (Supposing that you want your image below the textView)
var newFrame = cell.textView.frame
newFrame.y += 5
imageForCell.frame = newFrame
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.
It there a way to use both UITableViewCell and ASCellNode in the same UITableView ?
I have many cells but only some of them have performance issues and AsyncDisplayKit works very well with them. I'm wondering if I have to convert all my UITableViewCell's subclasses in order to use them in an ASTableView.
I've got it working using this initializer in AsyncDisplayKit v2.4, Swift:
ASCellNode(viewControllerBlock: { () -> UIViewController }, didLoad: { (ASDisplayNode) -> Void })
Here's a working example:
func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) - > ASCellNodeBlock {
let content = self.contentList[indexPath.row]
// Return the old cell only for specific rows
if content.type == "MyOldContentThatUsesUITableViewCell" {
return {
// Load OldContentCell through a view block on the main thread
let cellHeight: CGFloat = 367.0
let cellNode = ASCellNode(viewControllerBlock: { () - > UIViewController in
let viewController = UIViewController()
// Load a cell using xib
guard let cell = Bundle.main.loadNibNamed("OldContentCell", owner: self, options: nil) ? [0] as ? OldContentCell else {
return viewController
}
// Since it's on the main thread, we can use UIScreen to get the full width
cell.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: cellHeight)
viewController.view.addSubview(cell)
return viewController
}, didLoad: nil)
// Width here doesn't really matter.. it will try and expand the cell's width
// to the ASTableNode's width
cellNode.style.preferredSize = CGSize(width: 100.0, height: cellHeight)
return cellNode
}
}
// Return a real ASCellNode totally managed by AsyncDisplayKit
let cellNodeBlock: () -> ASCellNode = {
// My subclass of ASCellNode
return ContentCellNode(content: content, contentIndex: indexPath.row)
}
return cellNodeBlock
}
I have also tried using the view block instead of view controller block that did not work.. the block never got executed:
ASCellNode(viewBlock: { () -> UIView })