I have two view controllers – SecondTestViewController and SecondContentViewController. SecondContentViewController is a floating panel inside SecondTestViewController. SecondTestViewController has a UITableView and SecondContentViewController has a UICollectionView.
SecondContentViewController:
override func viewDidLoad() {
super.viewDidLoad()
// let layout = UICollectionViewFlowLayout()
// layout.scrollDirection = .vertical
myCollectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout())
guard let myCollectionView = myCollectionView else {
return
}
myCollectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.backgroundColor = .white
myCollectionView.translatesAutoresizingMaskIntoConstraints = false
myCollectionView.allowsSelection = true
myCollectionView.isSpringLoaded = true
view.backgroundColor = .white
view.addSubview(myCollectionView)
NSLayoutConstraint.activate([
myCollectionView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
myCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
myCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
myCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
private func collectionViewLayout() -> UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
let cellWidthHeightConstant: CGFloat = UIScreen.main.bounds.width * 0.2
layout.sectionInset = UIEdgeInsets(top: 50,
left: 10,
bottom: 0,
right: 10)
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = 0
layout.itemSize = CGSize(width: cellWidthHeightConstant, height: cellWidthHeightConstant)
return layout
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return contentData.count
print("numberOfItemsInSection activated")
print("numberOfItemsInSection = \(reviewDataController?.tableViewReviewData.count ?? 100)")
return reviewDataController?.tableViewReviewData.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("cellForItemAt activated")
let cell = myCollectionView?.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
// cell.data = self.contentData[indexPath.item]
cell.data = self.reviewDataController.tableViewReviewData[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width / 5, height: collectionView.frame.height / 10)
}
func updateTheCollectionView() {
print("updateCollectionView activated")
print("reviewData count = \(reviewDataController.tableViewReviewData.count)")
myCollectionView?.reloadData()
}
When a cell in SecondTestViewController's tableView is tapped, it takes a photo, passes the data, and then calls SecondContentViewController's updateTheCollectionView() function.
This function is called in SecondTestViewController after a photo has been taken and various other tasks completed (like updating the variable, etc.).
func passDataToContentVC() {
let contentVC = SecondContentViewController()
contentVC.reviewDataController = self.reviewDataController
contentVC.updateTheCollectionView()
}
As you can see, reloadData is supposed to be called inside this function. It should now update the view with the new photo. For some reason, it's like reloadData is never called because neither numberOfItemsInSection nor cellForItemsAt are being activated a second time.
What is going wrong? The print statements show that the data is being passed (they show an updated quantity after a photo has been taken). The view simply is not updating with the new data because the two collectionView functions are not being called.
Edit:
Here is the floating panel code.
fpc = FloatingPanelController()
fpc.delegate = self
guard let contentVC = storyboard?.instantiateViewController(identifier: "fpc_secondContent") as? SecondContentViewController else {
return
}
fpc.isRemovalInteractionEnabled = false
fpc.set(contentViewController: contentVC)
fpc.addPanel(toParent: self)
It's essentially inside of viewDidLoad for the SecondTestViewController.
For more information on how it works: Visit this github
As far as the didSelectRowAt function goes, it's too much code to show here. reviewDataController is dependency injection made up of an array of a struct. Essentially all that happens is when a cell is tapped, it pulls up the camera and allows the user to take a photo, then it stores the photo in the array inside of reviewDataController along with some other properties in the struct that the array is made of. The important part is that it's supposed to pass this data to the floating panel which then updates the collectionView showing the photo that was just taken.
Because you are passing data from SecondTestViewController to SecondContentViewController, which is forwards and not backwards, you don't need delegates or anything.
Instead, all you need to do is retain a reference to SecondContentViewController. Try something like this:
class SecondTestViewController {
var secondContentReference: SecondContentViewController? /// keep a reference here
override func viewDidLoad() {
super.viewDidLoad()
let fpc = FloatingPanelController()
fpc.delegate = self
guard let contentVC = storyboard?.instantiateViewController(identifier: "fpc_secondContent") as? SecondContentViewController else {
return
}
fpc.isRemovalInteractionEnabled = false
fpc.set(contentViewController: contentVC)
fpc.addPanel(toParent: self)
self.secondContentReference = contentVC /// assign contentVC to the reference
}
}
secondContentReference keeps a reference to your SecondContentViewController. Now, inside passDataToContentVC, use that instead of making a new instance with let contentVC = SecondContentViewController().
Replace
func passDataToContentVC() {
let contentVC = SecondContentViewController()
contentVC.reviewDataController = self.reviewDataController
contentVC.updateTheCollectionView()
}
... with
func passDataToContentVC() {
secondContentReference?.reviewDataController = self.reviewDataController
secondContentReference?.updateTheCollectionView()
}
It seems that
let contentVC = SecondContentViewController() // this is a new instance
contentVC.reviewDataController = self.reviewDataController
contentVC.updateTheCollectionView()
You create an instance and add it to SecondTestViewController but passDataToContentVC has another new one , hence no updates for the shown one
Related
I want to create this page. And I created a collectionView inside viewController. But how can I make the height of this dynamic and i think i need to create a scroll view? How do I change the collection view size than json data loaded? And how should the structure on this page?
I created programmatically collection view but I don't have any data now. But when I create collection view I have to give height value. I must when I loaded json data than I will set frame details. Please help this topics.. At least give mind please.. Thank you all
Edit:
I added scroll view inside cell but switch between cells not working now. In red area(cell) working switching but not working on containerView..
Scroll View problem
class NewsDetailController: UIViewController {
private lazy var collectionView: UICollectionView = {
let frame = CGRect(x: 0, y: 100, width: view.frame.width, height: 800)
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.isPagingEnabled = true
cv.delegate = self
cv.dataSource = self
cv.showsHorizontalScrollIndicator = false
cv.register(NewsDetailCell.self, forCellWithReuseIdentifier: reuseIdentifier)
return cv
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
// MARK: - Helpers
func configureUI() {
view.addSubview(collectionView)
view.backgroundColor = .white
view.addSubview(stackBottomButtons)
stackBottomButtons.anchor(left: view.leftAnchor,bottom: view.bottomAnchor,right: view.rightAnchor,paddingLeft: 16,paddingBottom: 32,paddingRight: 16, height: 60)
backButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
shareButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! NewsDetailCell
return cell
}
}
extension NewsDetailController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 800)
}
}
class NewsDetailCell: UICollectionViewCell {
// MARK: - Properties
let contentLabel: UILabel = {
let label = UILabel(frame: CGRect.zero)
label.numberOfLines = 0
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
configure()
configureUI()
}
func configure() {
contentLabel.text = articleViewModel?.article.content
}
func configureUI() {
addSubview(contentLabel)
contentLabel.anchor(top: stack.bottomAnchor,left: leftAnchor,right: rightAnchor)
}
}
I want to make this page.
Full Code
I think you are looking for something like this
To make the frame size dynamic you you need to set the cell size by the attributes combined height inside of the cells this can be done in the customCell file you create using this link
I have a UIcollectionView with images. When I rotate the screen, the cells do not resize as expected. I have tried adding collectionView.collectionViewLayout.invalidateLayout() to the viewDidLayoutSubviews() - but this only causes a crash. Please can someone advise?
I have a UIcollectionView with images. When I rotate the screen, the cells do not resize as expected. I have tried adding collectionView.collectionViewLayout.invalidateLayout() to the viewDidLayoutSubviews() - but this only causes a crash. Please can someone advise?
Portrait:
Landdscape:
class GridPicksCollectionViewController: UICollectionViewController{
let cellId = "gridyPickCell"
var numCelPerRow: CGFloat = 3
var layout: UICollectionViewFlowLayout!
let borderInset: CGFloat = 3
let interCellSpacing: CGFloat = 3
let spacingBetweenRows: CGFloat = 3
var dataSource = [UIImage]()
var collectionViewWidth: CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
generalSetup()
setUpDateSource()
setupCollectionViewLayout()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
// Update collectionViewWidth upon rotation
collectionViewWidth = self.collectionView.frame.width
//collectionView.collectionViewLayout.invalidateLayout() - causes app to crash
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
print("viewWillTransition")
}
private func generalSetup(){
navigationController?.navigationBar.barTintColor = UIColor.appDarkGreen
navigationItem.title = "Girdy Picks"
let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
navigationController?.navigationBar.titleTextAttributes = textAttributes
collectionView.backgroundColor = UIColor.white
collectionViewWidth = collectionView.frame.width
// Make sure collectionView is always within the safe area layout
if #available(iOS 11.0, *) {
collectionView?.contentInsetAdjustmentBehavior = .always
}
// Register UICollectionViewCell
collectionView.register(GirdyPickCollectionCell.self, forCellWithReuseIdentifier: cellId)
}
private func setUpDateSource(){
let images: [UIImage] = [UIImage.init(named: "buddha")!, UIImage.init(named: "sharpener")!, UIImage.init(named: "cars")!, UIImage.init(named: "houses")!, UIImage.init(named: "houses2")!, UIImage.init(named: "tower1")!, UIImage.init(named: "tower2")!]
dataSource = images
}
private func setupCollectionViewLayout(){
collectionView.delegate = self
collectionView.dataSource = self
layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout.minimumInteritemSpacing = interCellSpacing // distance between cells in a row
layout.minimumLineSpacing = spacingBetweenRows // distance in between rows
layout.sectionInset = UIEdgeInsets.init(top: borderInset, left: borderInset, bottom: borderInset, right: borderInset) // border inset for collectionView
}
}
extension GridPicksCollectionViewController: UICollectionViewDelegateFlowLayout{
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GirdyPickCollectionCell
cell.imageView.image = dataSource[indexPath.row]
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedImage = dataSource[indexPath.row]
let showImageVC = ImageGridViewController()
showImageVC.modalPresentationStyle = .fullScreen
showImageVC.imageToDisplay = selectedImage
self.present(showImageVC, animated: true) {}
}
// Determine size for UICollectionCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
print("sizeForItemAt")
guard let collectionWidth = collectionViewWidth else{ return CGSize.init()}
let widthCell = (collectionWidth - interCellSpacing * 2 - borderInset * 2) / numCelPerRow
return CGSize.init(width: widthCell, height: widthCell)
}
}
You are wrong about your lifecycle of events. When the device is rotated, your view hierarchy becomes notified of this event, and views start to re-layout themselves based on the information they have. After this has been finished, viewDidLayoutSubviews() will be called, not before.
Since you are updating the collectionViewWidth property in this method, the layout still uses the old value when calling collectionView(_:layout:sizeForItemAt:). You need to set this value in viewWillLayoutSubviews().
Alternatively, you can write a setter for this property which will call invalidateLayout() on the collection view's layout, but this will cause an unnecessary layout pass.
I am busy with a speech app with a soundboard when you tap on a CollectionView Cell then iPhone will speak the text. I have a little bug in my app and I don't know what the reason is.
I have built a CollectionView with images as backgroundViews. It works but when I go to an other view ,for example to the Paint view, and I will go back then the cells will confuse.
Can anybody tell me what goes wrong and how I solve it?
Thanks!
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
loadData()
let itemSize = UIScreen.main.bounds.width/2 - 5
let itemHeight = itemSize / 2
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets.init(top: 3, left: 3, bottom: 3, right: 3)
layout.itemSize = CGSize(width: itemSize, height: itemHeight)
layout.minimumInteritemSpacing = 3
layout.minimumLineSpacing = 3
soundboard.collectionViewLayout = layout
navigationItem.rightBarButtonItem = editButtonItem
if(traitCollection.forceTouchCapability == .available){
registerForPreviewing(with: self as UIViewControllerPreviewingDelegate, sourceView: collectionView)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
loadData()
}
func loadData() {
let soundRequest:NSFetchRequest<Soundboard> = Soundboard.fetchRequest()
do {
soundBoardData = try managedObjectContext.fetch(soundRequest)
self.soundboard.reloadData()
} catch {
print("Error")
}
}
// MARK: - Collection View
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return soundBoardData.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> soundboardCellVC {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! soundboardCellVC
let soundItem = soundBoardData[indexPath.row]
if let getCellPhoto = soundItem.photo as Data? {
cell.title.text = "";
let cellPhoto = UIImage(data: getCellPhoto)
let cellPhotoFrame = UIImageView(image:cellPhoto)
cellPhotoFrame.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height)
cellPhotoFrame.contentMode = UIView.ContentMode.scaleAspectFill
cell.backgroundView = UIView()
cell.backgroundView!.addSubview(cellPhotoFrame)
}
else
{
cell.title.text = soundItem.title;
cell.backgroundColor = UIColor(red: CGFloat(soundItem.colorRed), green: CGFloat(soundItem.colorGreen), blue: CGFloat(soundItem.colorBlue), alpha: 1)
let fontAwesomeIcon = FAType(rawValue: Helper.FANames.index(of: soundItem.icon!)!)
cell.icon.setFAIconWithName(icon: fontAwesomeIcon!, textColor: UIColor.white)
}
cell.layer.cornerRadius = 10
cell.layer.masksToBounds = true
cell.delegate = self as SoundboardCellDelegate
return cell
}
A good practice when working with cells is to clear any properties that you don't want to persist when the cell gets reused.
https://developer.apple.com/documentation/uikit/uicollectionreusableview/1620141-prepareforreuse
For example in your cells class you can do:
override func prepareForReuse() {
super.prepareForReuse()
self.title.text = nil
}
This keeps your cellForItem method cleaner, and you know for sure that the above actions will have been carried out before you reuse it.
Cells are re-used, which means that you are adding new image views to cells that have existing image views.
You should create a single UIImageView that is in your soundboardCellVC class and simply put the image into that, or set the image to nil as required.
A couple of other style points:
By convention, classes should start with an uppercase letter and it is a cell subclass, not a View Controller subclass, so it should be something like SoundboardCell not soundboardCellVC
Finally, your cellForItemAt signature should match the protocol declaration and be declared as returning a UICollectionViewCell, not your specific cell subclass:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
I have a CollectionView which dequeues a cell depending on the message type (eg; text, image).
The problem I am having is that when I scroll up/down the scroll is really choppy and thus not a very good user experience. This only happens the first time the cells are loaded, after that the scrolling is smooth.
Any ideas how I can fix this?, could this be an issue with the time its taking to fetch data before the cell is displayed?
I am not too familiar with running tasks on background threads etc. and not sure what changes I can make to prefect the data pre/fetching etc.. please help!
The Gif shows scroll up when the view loads, it shows the cells/view being choppy as I attempt to scroll up.
This is my func loadConversation() which loads the messages array
func loadConversation(){
DataService.run.observeUsersMessagesFor(forUserId: chatPartnerId!) { (chatLog) in
self.messages = chatLog
DispatchQueue.main.async {
self.collectionView.reloadData()
if self.messages.count > 0 {
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .bottom , animated: false)
}
}
}//observeUsersMessagesFor
}//end func
This is my cellForItemAt which dequeues cells
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let message = messages[indexPath.item]
let uid = Auth.auth().currentUser?.uid
if message.fromId == uid {
if message.imageUrl != nil {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellImage", for: indexPath) as! ConversationCellImage
cell.configureCell(message: message)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellSender", for: indexPath) as! ConversationCellSender
cell.configureCell(message: message)
return cell
}//end if message.imageUrl != nil
} else {
if message.imageUrl != nil {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellImageSender", for: indexPath) as! ConversationCellImageSender
cell.configureCell(message: message)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCell", for: indexPath) as! ConversationCell
cell.configureCell(message: message)
return cell
}
}//end if uid
}//end func
This is my ConversationCell class which configures a custom cell for dequeueing by cellForItemAt (note: in addition there another ConversationCellImage custom cell class which configures an image message):
class ConversationCell: UICollectionViewCell {
#IBOutlet weak var chatPartnerProfileImg: CircleImage!
#IBOutlet weak var messageLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
chatPartnerProfileImg.isHidden = false
}//end func
func configureCell(message: Message){
messageLbl.text = message.message
let partnerId = message.chatPartnerId()
DataService.run.getUserInfo(forUserId: partnerId!) { (user) in
let url = URL(string: user.profilePictureURL)
self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage: #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
}//end getUserInfo
}//end func
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = 10.0
self.layer.shadowRadius = 5.0
self.layer.shadowOpacity = 0.3
self.layer.shadowOffset = CGSize(width: 5.0, height: 10.0)
self.clipsToBounds = false
}//end func
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//toggles auto-layout
setNeedsLayout()
layoutIfNeeded()
//Tries to fit contentView to the target size in layoutAttributes
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
//Update layoutAttributes with height that was just calculated
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height) + 18
layoutAttributes.frame = frame
return layoutAttributes
}
}//end class
Time Profile results:
Edit: Flowlayout code
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout,
let collectionView = collectionView {
let w = collectionView.frame.width - 40
flowLayout.estimatedItemSize = CGSize(width: w, height: 200)
}// end if-let
Edit: preferredLayoutAttributesFitting function in my custom cell class
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//toggles auto-layout
setNeedsLayout()
layoutIfNeeded()
//Tries to fit contentView to the target size in layoutAttributes
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
//Update layoutAttributes with height that was just calculated
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height) + 18
layoutAttributes.frame = frame
return layoutAttributes
}
SOLUTION:
extension ConversationVC: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat = 80
let message = messages[indexPath.item]
if let text = message.message {
height = estimateFrameForText(text).height + 20
} else if let imageWidth = message.imageWidth?.floatValue, let imageHeight = message.imageHeight?.floatValue{
height = CGFloat(imageHeight / imageWidth * 200)
}
let width = collectionView.frame.width - 40
return CGSize(width: width, height: height)
}
fileprivate func estimateFrameForText(_ text: String) -> CGRect {
let size = CGSize(width: 200, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)], context: nil)
}
}//end extension
First thing first, let's try finding the exact location where this problem is arising.
Try 1:
comment this line
//self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage: #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
And run your app to see the results.
Try 2:
Put that line in async block to see the results.
DispatchQueue.main.async {
self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage: #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
}
Try 3: Comment the code for setting corner radius
/*self.layer.cornerRadius = 10.0
self.layer.shadowRadius = 5.0
self.layer.shadowOpacity = 0.3
self.layer.shadowOffset = CGSize(width: 5.0, height: 10.0)
self.clipsToBounds = false*/
Share your results for Try 1, 2 and 3 and then we can get the better idea about where the problem lies.
Hope this way we can get the reason behind flickering.
Always make sure that the data like image or GIF shouldn't download on the main thread.
This is the reason why your scrolling is not smooth. Download the data in separate thread in background use either GCD or NSOperation queue. Then show the downloaded image always on main thread.
use AlamofireImage pods, it will automatically handle the downloaded task in background.
import AlamofireImage
extension UIImageView {
func downloadImage(imageURL: String?, placeholderImage: UIImage? = nil) {
if let imageurl = imageURL {
self.af_setImage(withURL: NSURL(string: imageurl)! as URL, placeholderImage: placeholderImage) { (imageResult) in
if let img = imageResult.result.value {
self.image = img.resizeImageWith(newSize: self.frame.size)
self.contentMode = .scaleAspectFill
}
}
} else {
self.image = placeholderImage
}
}
}
I think the choppiness you are seeing is because the cells are given a size and then override the size they were given which causes the layout to shift around. What you need to do is do the calculation during the creation of the layout first time through.
I have a function that I have used like this...
func height(forWidth width: CGFloat) -> CGFloat {
// do the height calculation here
}
This is then used by the layout to determine the correct size first time without having to change it.
You could have this as a static method on the cell or on the data... or something.
What it needs to do is create a cell (not dequeue, just create a single cell) then populate the data in it. Then do the resizing stuff. Then use that height in the layout when doing the first layout pass.
Yours is choppy because the collection lays out the cells with a height of 20 (for example) and then calculates where everything needs to be based on that... then you go... "actually, this should be 38" and the collection has to move everything around now that you've given it a different height. This then happens for every cell and so causes the choppiness.
I might be able to help a bit more if I could see your layout code.
EDIT
Instead of using that preferredAttributes method you should implement the delegate method func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize.
Do something like this...
This method goes into the view controller.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let message = messages[indexPath.item]
let height: CGFloat
if let url = message.imageURL {
height = // whatever height you want for the images
} else {
height = // whatever height you want for the text
}
return CGSize(width: collectionView.frame.width - 40, height: height)
}
You may have to add to this but it will give you an idea.
Once you've done this... remove all your code from the cell to do with changing the frames and attributes etc.
Also... put your shadow code into the awakeFromNib method.
I am trying to add a back/ return button to my UICollectionView, I have this code so far to implement the button:
import UIKit
class EmojiPopup: UIView,UICollectionViewDataSource,UICollectionViewDelegate
{
var collocationView : UICollectionView!
var arrImagesList:NSMutableArray!
var blur:UIBlurEffect = UIBlurEffect()
override init(frame: CGRect)
{
super.init(frame: frame)
arrImagesList = NSMutableArray()
self.backgroundColor = UIColor.purpleColor().colorWithAlphaComponent(0.2)
let layout = UICollectionViewFlowLayout()
//header gap
layout.headerReferenceSize = CGSizeMake(20,20)
//collection view item size
layout.itemSize = CGSizeMake(70, 70)
layout.minimumInteritemSpacing = 25
layout.minimumLineSpacing = 25
collocationView = UICollectionView(frame: CGRectMake(50,50,UIScreen.mainScreen().bounds.screenWidth - 100,UIScreen.mainScreen().bounds.screenHeight - 100), collectionViewLayout: layout)
self.addSubview(collocationView)
// Create the blurEffect and apply to view
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.ExtraLight)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.alpha = 0.7
blurEffectView.frame = self.bounds
self.addSubview(blurEffectView)
collocationView.backgroundColor = UIColor.purpleColor().colorWithAlphaComponent(0.002)
collocationView.dataSource = self
collocationView.delegate = self
collocationView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellIdentifier")
//hide scrollbar
self.collocationView.showsVerticalScrollIndicator = false
//back button
let btnBack = UIButton(frame:TCRectMake(x:138 ,y:523,width:45,height:45))
btnBack.setImage(UIImage(named:"back"), forState: UIControlState.Normal)
btnBack.addTarget(self, action:"btnBackClick", forControlEvents: UIControlEvents.TouchUpInside)
self.addSubview(btnBack)
//back button func
func btnBackClick()
{
}
let fm = NSFileManager.defaultManager()
let path = NSBundle.mainBundle().resourcePath!
let items = try! fm.contentsOfDirectoryAtPath(path)
for item in items
{
if item.hasSuffix("png") && item.containsString("#") == false && item.containsString("AppIcon") == false && item.containsString("tick_blue") == false && item.containsString("video_camera") == false
{
arrImagesList.addObject(item)
}
}
}
var completeHandler:((String)->())?
func showDetails(viewParent:UIView,doneButtonClick:((String)->())?)
{
completeHandler = doneButtonClick
viewParent.addSubview(self)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return arrImagesList.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let identifier="ImageCell\(indexPath.section)\(indexPath.row)"
collectionView.registerClass(ImageViewCell.self, forCellWithReuseIdentifier:identifier)
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier, forIndexPath: indexPath) as! ImageViewCell
cell.backgroundColor = UIColor(white:1, alpha:0)
cell.imgView.image = UIImage(named:arrImagesList[indexPath.row] as! String)
cell.imgView.backgroundColor = UIColor.clearColor()
cell.imgView.opaque = false
cell.imgView.contentMode = .ScaleAspectFit
//keeps blur to background
self.bringSubviewToFront(collocationView)
return cell
}
// func collectionView(collectionView: UICollectionView,
// layout collectionViewLayout: UICollectionViewLayout,
// sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
// {
// let width=UIScreen.mainScreen().bounds.size.width-50
// return CGSize(width:width/3, height:width/3)
// }
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
//let cell=collectionView.cellForItemAtIndexPath(indexPath) as! ImageViewCell
UIView.animateWithDuration(0.3, animations:{
self.collocationView.alpha=0
}, completion: { finished in
if self.completeHandler != nil
{
self.completeHandler!(self.arrImagesList[indexPath.row] as! String)
}
self.removeFromSuperview()
})
}
func showDetails(viewParent:UIView,dictData : [String:String],index:Int,doneButtonClick:(()->())?,cancelBUttonClick:(()->())?)
{
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I want the collection view to be closed if the user presses the back button, but I am not sure what to enter in the back button function. I want the user to be returned back to the main view controller (mapview) if possible
I will assume you are talking about a UICollectionViewController and not about a UICollectionView. A UICollectionViewController has a UICollectionView inside. You can "close" (dismiss) a UICollectionViewController but not a UICollectionView. You can even dismiss a UIViewController that has a UICollectionView inside.
You have two options:
Put you collection view controller (and the main view controller) inside a navigation controller so you can use the default back button already implemented by the navigation controller.
You can present the collection view controller modally from the main view controller. Then you need to add a close button (Not back button) that dismiss the controller (The main view controller will stay "behind" so when you dismiss the UICollectionViewController it will become visible again.
It's a long way. I suggest you read this getting started guide from Apple, there you can figure it out how navigation controllers and what they do. This is something you need to learn when developing with Swift. I suggest you go further and read the whole tutorial. After reading that chapter you should understand the navigation flow of an iOS application and implement the back-button navigation.
If you find any trouble following that tutorial, let me know.