I have a UICollectionView in my UIViewController(wrapped in UINavigationContoller). In the UICollectionViewCell, I create a UIImageView with a gif url.(Using Kingfisher)
The imageView works as expected, but it's getting blank whenever I "try to" swipe back to previous UIViewController. And when I cancel the "swipe back gesture", the imageView shows the gif again.
Oddly enough, the imageView is not getting blank when I click the back indicator.
Also, if I set a static image to the imageView, the imageView does not get blank.
Does anyone know how to fix this issue?
Here is a demo:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.pushViewController(ViewController2(), animated: true)
}
}
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.clearColor()
collectionView.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(collectionView)
collectionView.snp_makeConstraints { (make) in
make.top.equalTo(snp_topLayoutGuideBottom)
make.left.right.bottom.equalTo(view)
}
}
}
extension ViewController2: UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionViewCell
cell.imageView.animationImages = [UIImage(named: "1")!, UIImage(named: "2")!]
cell.imageView.startAnimating()
return cell
}
}
class CollectionViewCell: UICollectionViewCell {
lazy var imageView: UIImageView = {
let view = UIImageView()
view.clipsToBounds = true
view.contentMode = .ScaleAspectFill
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(imageView)
imageView.snp_makeConstraints { (make) in
make.edges.equalTo(contentView)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I think this is a UIImageView issue.
Related
I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In my VC I have a view that pulls up from the bottom. I setup and add a UICollectionView in the viewDidLoad():
//Add and setup the collectionView
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: flowLayout)
collectionView?.register(PhotoCell.self, forCellWithReuseIdentifier: "photoCell")
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = #colorLiteral(red: 0.9771530032, green: 0.7062081099, blue: 0.1748393774, alpha: 1)
collectionView?.allowsMultipleSelection = false
collectionView?.allowsSelection = true
pullUpView.addSubview(collectionView!)
pullUpView.bringSubview(toFront: collectionView!)
The UICollectionView Delegate methods are in an extension, for now in the same codefile as the VC:
//MARK: - Extension CollectionView
extension MapVC: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imagesArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? PhotoCell {
let imageFromIndex = imagesArray[indexPath.item]
let imageView = UIImageView(image: imageFromIndex )
cell.addSubview(imageView)
return cell
} else {
return PhotoCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selected")
//Create PopVC instance, using storyboard id set in storyboard
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") as? PopVC else { return }
popVC.passedImage = imagesArray[indexPath.row]
present(popVC, animated: true, completion: nil)
}
}
The problem is that when I tap on a cell nothing happens. I've put a print statement inside the didSelectItemAt method, but that never gets printed. So, my cells never get selected or at least the didSelectItemAt method never gets triggered!
Been debugging and trying for hours, and I can't see what's wrong. Any help appreciated. Perhaps someone could open mijn project from Github to see what's wrong, if that is allowed ?
UPDATE:
Using Debug View Hierarchy, I see something disturbing: Each photoCell has multiple (many!) UIImageViews. I think that should be just one UIImageView per photoCell. I don't know what is causing this behaviour?
Debug View Hierarchy
I checked your code, there are few problems:
First of all you have to change your PhotoCell implementation, and add your imageView inside the class, only when the cell is created. Your cell is not loading a XIB so you have to add the imageView in init(frame:):
class PhotoCell: UICollectionViewCell {
var photoImageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
setupCell()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupCell() {
photoImageView = UIImageView()
addSubview(photoImageView)
}
override func layoutSubviews() {
super.layoutSubviews()
photoImageView.frame = bounds // ensure that imageView size is the same of the cell itself
}
}
After this change, in cellForItem method you can do cell.photoImageView.image = imageFromIndex.
The problem of didSelect not called is caused by the fact your pullUpViewis always with height = 1, even if you're able to see the collectionView, it will not receive any touch.
First add an IBOutlet of the height constraint of pullUpView in your MapVc
When creating collection view, ensure the size of collection view is the same of the pullUpView, so it will be able to scroll; collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 300), collectionViewLayout: flowLayout)
Then change animateViewUp and animateViewDown to this
func animateViewUp() {
mapViewBottomConstraint.constant = 300
pullUpViewHeightConstraint.constant = 300 // this will increase the height of pullUpView
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
#objc func animateViewDown() {
cancelAllSessions()
//remove previous loaded images and urls
imagesArray.removeAll()
imageUrlsArray.removeAll()
mapViewBottomConstraint.constant = 0
pullUpViewHeightConstraint.constant = 0 // this will reset height to 0
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
By doing all of this the swipe down gesture will not work anymore because the touch is intercepted and handled by the collection view, you should handle this manually.
However I suggest you to change the online course, there are a lot of things that I don't like about this code.
I have a UICollectionViewController (HomeViewController) with 3 cells.
Each cell is as big as the screen's size.
In the last cell I have another UICollectionView (collectionView) to display data.
This data should be refreshed every time I pull to refresh.
But that does not work.
This is the code so far:
HomeViewController
class HomeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let thirdCellId = "thirdCell"
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let thirdCell = collectionView.dequeueReusableCell(withReuseIdentifier: thirdCellId, for: indexPath) as! ThirdCell
thirdCell.refreshControl.addTarget(self, action: #selector(refreshData), for: UIControlEvents.valueChanged)
return thirdCell
}
#objc func refreshData(sender: UIRefreshControl) {
let cell = sender.superview as! ThirdCell <--- gives error: Could not cast value of type 'UICollectionView' to 'ExploreCell'
cell.loadData()
cell.refreshControl.endRefreshing()
print("Did work")
}
}
ThirdCell code:
class ThirdCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
override init(frame: CGRect) {
super .init(frame: frame)
setupCollectionView()
loadData()
setupRefreshControl()
}
// --- CollectionView ---
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
return cv
}()
func setupCollectionView() {
self.addSubview(collectionView)
// Setup constraints
}
// --- RefreshControl ---
var refreshControl: UIRefreshControl = {
let rfs = UIRefreshControl()
// Handle action in HomeViewController
return rfs
}()
func setupRefreshControl() {
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
refreshControl.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
refreshControl.bottomAnchor.constraint(equalTo: self.topAnchor, constant: 30).isActive = true
refreshControl.translatesAutoresizingMaskIntoConstraints = false
}
func loadData() {
// Load data
self.collectionView.reloadData()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I'm trying to pass an image from a view controller to a UICollectionViewCell whenever I segue to the UICollectionViewController. Normally, I would just use the following code to pass the variable directly to the UICollectionViewController.
let myCollectionViewController = MyCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
myCollectionViewController.selectedImage = myImageView?.image
navigationController?.pushViewController(myCollectionViewController, animated: true)
However, I have subclassed my UICollectionViewCell and have set up the cell as follows:
import UIKit
class myCollectionViewCell: UICollectionViewCell {
let imageView:UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
var selectedImage: UIImage? {
didSet {
self.imageView.image = selectedImage
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How do I directly pass my image directly to my UICollectionViewCell subclass during the segue?
Hope this helps you-:
import Foundation
class HomeController: UIViewController{
// STORED VARIABLE FOR CollectionView
lazy var CollectionView : UICollectionView = {
var layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
var collectionViews = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionViews.translatesAutoresizingMaskIntoConstraints = false
collectionViews.backgroundColor = UIColor.white
collectionViews.showsHorizontalScrollIndicator = false
collectionViews.showsVerticalScrollIndicator = false
collectionViews.dataSource = self
collectionViews.delegate = self
return collectionViews
}()
// APPLY CONSTRAINTS FOR CollectionView
func setUpCollectionView(){
view.addSubview(CollectionView)
CollectionView.register(HomeControllerCell.self, forCellWithReuseIdentifier: "cell")
CollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
CollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
CollectionView.topAnchor.constraint(equalTo: view.topAnchor,constant:92).isActive = true
CollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant:-50).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
}
}
// EXTENSION FOR COLLECTION VIEW PARENT
extension HomeController:UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{
// NUMBER OF SECTION IN TABLE
public func numberOfSections(in collectionView: UICollectionView) -> Int{
return 1
}
// NUMBER OF ROWS IN PARENT SECTION
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return 5
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let Cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeControllerCell
// PASS IMAGE (whatever you have) TO COMPUTED VARIABLE image
Cell.image = pass image here
return Cell
}
// SIZE FOR PARENT CELL COLLECTION VIEW
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: view.frame.width, height: 220)
}
}
CollectionViewCellClass-:
class HomeControllerCell: UICollectionViewCell {
//INITIALIZER
override init(frame: CGRect) {
super.init(frame: frame)
setUpview()
}
// STORED VARIABLE imageVIEW
let imageView:UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = true
return iv
}()
// COMPUTED VARIABLE image
var image: UIImage? {
didSet {
self.imageView.image = image
}
}
// APPLY CONSTRAINTS FOR CELL VIEWS
func setUpview(){
// ADD imageView AS SUBVIEW
addSubview(imageView)
// APPLY CONSTRAINT ON IMAGE VIEW
imageView.leftAnchor.constraint(equalTo: self.leftAnchor,constant:5).isActive = true
//menuHeader.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.topAnchor,constant:12).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
}
// REQUIRED INIT
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I'm starting a new project with a collection view that will be downloading a lot of data from a REST api. I've split the collection view and datasource into two files, but when I run the application, all I get is a black screen. I've seen a few questions and tried changing the background, adding the collection view as a subview, and nothing seems to be working. I don't run into any errors and the debug view hierarchy and the views are listed (back to front) as UIWindoW -> MainSearchVC -> UICollectionView.
I originally thought no cells were being filled in, but they should be since I set a UIImage in the cells. I'm not sure where else to look for this. My code is below - if anyone has experience with splitting the collection view from it's datasource or why things aren't working, please help:)
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainSearchVC()
return true
}
}
class MainSearchVC: UICollectionViewController {
init(){
super.init(collectionViewLayout: UICollectionViewFlowLayout())
self.collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: UICollectionViewFlowLayout())
self.collectionView?.dataSource = MainSearchDataSource()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
collectionView?.register(ImageCell.self, forCellWithReuseIdentifier: "imagecell")
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.backgroundColor = UIColor.clear
self.collectionView?.tintColor = UIColor.blue
NSLog("Visible Cells: " + String(describing: self.collectionView?.visibleCells.count))
self.view.addSubview(self.collectionView!)
}
}
class MainSearchDataSource: NSObject, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1;
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imagecell", for: indexPath)
cell.backgroundColor = UIColor.red
return cell
}
}
class ImageCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(contentsOfFile: "SR71")
iv.contentMode = .scaleAspectFill
return iv
}()
func setupViews(){
addSubview(imageView)
imageView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.width)
}
}
Problem with your MainSearchVC collection view dataSource delegate
First you should understand the Zeroing Weak Reference
dataSource delegate is maintaining a weak reference to your Object of MainSearchDataSource. And your statement is
self.collectionView?.dataSource = MainSearchDataSource()
Just allocated and assigned. It will not retain the datasource object.
You have to create a class variable and assign the MainSearchDataSource object. It will hold the object reference until MainSearchVC deallocate from memory.
class MainSearchVC: UICollectionViewController {
var searchDataSource: MainSearchDataSource?
init(){
super.init(collectionViewLayout: UICollectionViewFlowLayout())
self.collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: UICollectionViewFlowLayout())
searchDataSource = MainSearchDataSource()
self.collectionView?.dataSource = searchDataSource
}
// Remaining code of your `MainSearchVC`
}