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.
Related
My NSCollectionView item integrates another NSCollectionView. Everything works great but not when it comes to items selection in the first view.
When I set collectionView1.isSelectable = true, the didSelectItemsAt delegate is called only if I click somewhere else than the second (integrated) collectionView. To be clearer, if I click on a label, an image or a custom view: it works. As soon as I click on the second collection view, the delegate is not called.
Here is what I have tried so far:
Setting collectionView2.isSelectable = true to the second view.
That did not work.
I followed this tip. Curiously that did not work either. I got the same behaviour.
The only thing that works is to add a gesture recogniser on each integrated collection view. But this is so ugly...
I haven't tried on iOS yet with UICollectionView and cell selection, but I tend to think that the problem is the same.
EDIT:
So, if I click on the green label or everywhere on the blue part (which is the first collection view item), the didSelectItemsAt delegate is called correctly.
If I click on the orange NSView (with the second CollectionView inside), it's not called..
Heres is the simplified code of the first CollectionView:
class DashboardVC: NSViewController {
#IBOutlet weak var collectionView1: NSCollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.initViews()
}
fileprivate func initViews() {
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.itemSize = NSSize(width: 400.0, height: 380.0)
flowLayout.sectionInset = NSEdgeInsets(top: 20.0, left: 20.0, bottom: 20.0, right: 20.0)
flowLayout.minimumInteritemSpacing = 20.0
flowLayout.minimumLineSpacing = 20.0
self.collectionView1.collectionViewLayout = flowLayout
self.collectionView1.isSelectable = true
self.collectionView1.dataSource = self
self.collectionView1.delegate = self
}
}
extension DashboardVC: NSCollectionViewDataSource {
func numberOfSections(in collectionView: NSCollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ itemForRepresentedObjectAtcollectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = self.collectionView1.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for: indexPath)
guard let collectionViewItem = item as? CollectionViewItem else { return item }
return collectionViewItem
}
}
extension DashboardVC: NSCollectionViewDelegate {
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
print("I can't make this work!")
}
}
And for the second collection view:
class TabularView: NSView {
lazy var collectionView2: NSCollectionView = {
let gridLayout = NSCollectionViewGridLayout()
gridLayout.maximumNumberOfColumns = self.numberOfColums
gridLayout.maximumNumberOfRows = self.numberOfRows
let collectionView = NSCollectionView(frame: .zero)
collectionView.collectionViewLayout = gridLayout
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.isSelectable = true //-> does not work :(
collectionView.dataSource = self
collectionView.delegate = self
return collectionView
}()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.setup()
}
private func setup() {
self.addSubview(self.collectionView)
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: self.collectionView.topAnchor),
self.bottomAnchor.constraint(equalTo: self.collectionView.bottomAnchor),
self.leadingAnchor.constraint(equalTo: self.collectionView.leadingAnchor),
self.trailingAnchor.constraint(equalTo: self.collectionView.trailingAnchor),
])
}
}
extension TabularView: NSCollectionViewDataSource {
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfRows * self.numberOfColums
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "TabularCollectionViewItem"), for: indexPath)
guard let collectionViewItem = item as? TabularCollectionViewItem else { return item }
return collectionViewItem
}
}
If I'm understanding your problem correctly, you're trying to disable selection on a specific item in collectionView1 and instead have the user select the items of collectionView2 embedded in this cell.
Take a look at NSCollectionViewDelegate's collectionView(_:shouldSelectItemsAt:)
From the documentation:
Return Value
The set of NSIndexPath objects corresponding to the items that you want to be selected. If you do not want any items selected, return an empty set.
The problem:
I have a master UIPageViewController (MainPageVC) with three imbedded page views (A, B, & C) that are accessible both with swipe gestures and by pressing the appropriate locations in a custom page indicator* in the MainPageVC (*not a true UIPageControl but comprised of three ToggleButtons - a simple reimplementation of UIButton to become a toggle-button). My setup is as follows:
Previous reading:
Reliable way to track Page Index in a UIPageViewController - Swift, A reliable way to get UIPageViewController current index, and UIPageViewController: return the current visible view
indicated that the best way to do this was with didFinishAnimating calls, and manually keep track of the current page index, but I'm finding that this does not deal with certain edge cases.
I have been trying to produce a safe way of keeping track of the current page index (with didFinishAnimating and willTransitionTo methods) but am having trouble with the edge case where a user is in view A, and then swipes all the way across to C (without lifting up their finger), and then beyond C, and then releasing their finger... in this instance didFinishAnimating isn't called and the app still believes it is in A (i.e. A toggle button is still pressed and pageIndex is not updated correctly by the viewControllerBefore and viewControllerAfter methods).
My code:
#IBOutlet weak var pagerView: UIView!
#IBOutlet weak var aButton: ToggleButton!
#IBOutlet weak var bButton: ToggleButton!
#IBOutlet weak var cButton: ToggleButton!
let viewControllerNames = ["aVC", "bVC", "cVC"]
lazy var buttonsArray = {
[aButton, bButton, cButton]
}()
var previousPage = "aVC"
var pageVC: UIPageViewController?
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
print("TESTING - will transition to")
let currentViewControllerClass = String(describing: pageViewController.viewControllers![0].classForCoder);
let viewControllerIndex = viewControllerNames.index(of: currentViewControllerClass);
if currentViewControllerClass == previousPage {
return
}
let pastIndex = viewControllerNames.index(of: previousPage)
if buttonsArray[pastIndex!]?.isOn == true {
buttonsArray[pastIndex!]?.buttonPressed()
}
if let newPageButton = buttonsArray[viewControllerIndex!] {
newPageButton.buttonPressed()
}
self.previousPage = currentViewControllerClass
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
print("TESTING - did finish animating")
let currentViewControllerClass = String(describing: pageViewController.viewControllers![0].classForCoder)
let viewControllerIndex = viewControllerNames.index(of: currentViewControllerClass)
if currentViewControllerClass == previousPage {
return
}
let pastIndex = viewControllerNames.index(of: previousPage)
if buttonsArray[pastIndex!]?.isOn == true {
buttonsArray[pastIndex!]?.buttonPressed()
}
if let newPageButton = buttonsArray[viewControllerIndex!] {
newPageButton.buttonPressed()
}
self.previousPage = currentViewControllerClass
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let onboardingViewControllerClass = String(describing: viewController.classForCoder)
let viewControllerIndex = viewControllerNames.index(of: onboardingViewControllerClass)
let newViewControllerIndex = viewControllerIndex! - 1
if(newViewControllerIndex < 0) {
return nil
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: viewControllerNames[newViewControllerIndex])
if let vc = vc as? BaseTabVC {
vc.mainPageVC = self
vc.intendedCollectionViewHeight = pagerViewHeight
}
return vc
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let onboardingViewControllerClass = String(describing: viewController.classForCoder)
let viewControllerIndex = viewControllerNames.index(of: onboardingViewControllerClass)
let newViewControllerIndex = viewControllerIndex! + 1
if(newViewControllerIndex > viewControllerNames.count - 1) {
return nil
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: viewControllerNames[newViewControllerIndex])
if let vc = vc as? BaseTabVC {
vc.mainPageVC = self
vc.intendedCollectionViewHeight = pagerViewHeight
}
return vc
}
}
I'm at a loss as to how to deal with this edge case, the problem is that it can lead to fatal crashes of the app if the user then tries to press something in C that should otherwise be guaranteed to exist, and an unexpected nil or indexOutOfBounds error is thrown.
Very well written question. Especially for a newbie. (Voted.) You clearly state the problem you're having, including illustrations and your current code.
The solution I proposed in another thread was to subclass UIPageControl and have it implement a didSet on its currentPage property. You can then have the page control notify the view controller of the current page index. (By giving your custom subclass a delegate property, by sending a notification center message, or whatever method best fits your needs.)
(I did a simple test of this approach and it worked. I didn't test exhaustively however.)
The fact that the UIPageViewController reliably updates the page control but that there's no reliable, obvious way to figure out the current page index seems like an oversight in the design of this class.
Own Solution
I found the solution to this: don't use a UIPageView(Controller), use a CollectionView(Controller) instead. It is MUCH easier to keep track of the position of a collection view than to try and manually keep track of the current page in a UIPageViewController.
The solution is as follows:
Method
Refactor MainPagerVC as a CollectionView(Controller) (or as a regular VC that conforms to the UICollectionViewDelegate UICollectionViewDataSource protocols).
Set each page (aVC, bVC, and cVC) as a UICollectionViewCell subclass (MainCell).
Set each of these pages to fill the MainPagerVC.collectionView within the screen's bounds - CGSize(width: view.frame.width, height: collectionView.bounds.height).
Refactor the toggle-buttons at the top (A, B, and C) as three UICollectionViewCell subclasses (MenuCell) in a MenuController (itself a UICollectionViewController.
As collection views inherit from UIScrollView you can implement scrollViewDidScroll, scrollViewDidEndScrollingAnimation and scrollViewWillEndDragging methods, along with delegation (with didSelectItemAt indexPath) to couple the MainPagerVC and MenuController collection views.
Code
class MainPagerVC: UIViewController, UICollectionViewDelegateFlowLayout {
fileprivate let menuController = MenuVC(collectionViewLayout: UICollectionViewFlowLayout())
fileprivate let cellId = "cellId"
fileprivate let pages = ["aVC", "bVC", "cVC"]
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.showsVerticalScrollIndicator = false
cv.showsHorizontalScrollIndicator = false
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
menuController.delegate = self
setupLayout()
}
fileprivate func setupLayout() {
guard let menuView = menuController.view else { return }
view.addSubview(menuView)
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
//Setup constraints (placing the menuView above the collectionView
collectionView.register(MainCell.self, forCellWithReuseIdentifier: cellId)
//Make the collection view behave like a pager view (no overscroll, paging enabled)
collectionView.isPagingEnabled = true
collectionView.bounces = false
collectionView.allowsSelection = true
menuController.collectionView.selectItem(at: [0, 0], animated: true, scrollPosition: .centeredHorizontally)
}
}
extension MainPagerVC: MenuVCDelegate {
// Delegate method implementation (scroll to the right page when the corresponding Menu "Button"(Item) is pressed
func didTapMenuItem(indexPath: IndexPath) {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
extension MainPagerVC: UICollectionViewDelegate, UICollectionViewDataSource {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let offset = x / pages.count
menuController.menuBar.transform = CGAffineTransform(translationX: offset, y: 0)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
let item = Int(scrollView.contentOffset.x / view.frame.width)
let indexPath = IndexPath(item: item, section: 0)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .bottom)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let x = targetContentOffset.pointee.x
let item = Int(x / view.frame.width)
let indexPath = IndexPath(item: item, section: 0)
menuController.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MainCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: view.frame.width, height: collectionView.bounds.height)
}
}
class MainCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
// Custom UIColor extension to return a random colour (to check that everything is working)
backgroundColor = UIColor().random()
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
protocol MenuVCDelegate {
func didTapMenuItem(indexPath: IndexPath)
}
class MenuVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cellId = "cellId"
fileprivate let menuItems = ["A", "B", "C"]
var delegate: MenuVCDelegate?
//Sliding bar indicator (slightly different from original question - like Reddit)
let menuBar: UIView = {
let v = UIView()
v.backgroundColor = .red
return v
}()
//1px view to visually separate MenuBar region from "pager"-views
let menuSeparator: UIView = {
let v = UIView()
v.backgroundColor = .gray
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .white
collectionView.allowsSelection = true
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
}
//Add views and setup constraints for collection view, separator view and "selection indicator" view - the menuBar
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.didTapMenuItem(indexPath: indexPath)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuItems.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.label.text = menuItems[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.width
return .init(width: width/CGFloat(menuItems.count), height: view.frame.height)
}
}
class MenuCell: UICollectionViewCell {
let label: UILabel = {
let l = UILabel()
l.text = "Menu Item"
l.textAlignment = .center
l.textColor = .gray
return l
}()
override var isSelected: Bool {
didSet {
label.textColor = isSelected ? .black : .gray
}
}
override init(frame: CGRect) {
super.init(frame: frame)
//Add label to view and setup constraints to fill Cell
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
References
A "Lets Build That App" YouTube Video: "We Made It on /r/iosprogramming! Live coding swiping pages feature"
I have a collection in the ViewController1, if i click that it goes to ViewController2 where i can change a image; when i push back button on the navigation controller to go back in the ViewController1 i should see the image i changed in the ViewController2. My problem is that i need to reload the data of the CollectionView but i can't do it! I already tried to put CollectionView.reloaddata() in the **ViewWillAppear**, but nothing happened! How can i do this?
import UIKit
private let reuseIdentifier = "Cell2"
class CollectionViewControllerStatiVegetarian: UICollectionViewController {
let baza1 = Baza()
#IBOutlet var CollectionViewOut: UICollectionView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
CollectionViewOut.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
let backround = CAGradientLayer().turquoiseColor()
backround.frame = self.view.bounds
self.collectionView?.backgroundView = UIView()
self.collectionView?.backgroundView?.layer.insertSublayer(backround, at: 0)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let size2 = baza1.superTuples(name: "2")
let x = Mirror(reflecting: size2).children.count //
return Int(x+1)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as! CollectionViewCell_VegetarianStaty
if indexPath.row != 0 {
cell.shapka_stati_vegetarian.image = nil
let superTupl = baza1.superTuplesShapka(Nomer_tupl: (indexPath.row-1))
cell.label.text = superTupl.5
let tupl = baza1.superTuplesShapka(Nomer_tupl: (indexPath.row-1))
if (tupl.2 == 1) {
cell.shapka_stati_vegetarian.image = nil
cell.shapka_stati_vegetarian.image = UIImage(named: "fon_galochka.png")
} else {}
} else {
cell.shapka_stati_vegetarian.image = UIImage(named: "shapkastaty")
cell.label.text = ""
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let screenWidth = UIScreen.main.fixedCoordinateSpace.bounds.width
let height = screenWidth*550/900+20
var size = CGSize(width: screenWidth, height: 73)
if indexPath.row==0 {
size = CGSize(width: screenWidth, height: height)
}
return size
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row != 0 {
numb_cell = indexPath.row
let bazaSh = Baza()
let f = bazaSh.superTuplesShapka(Nomer_tupl: (indexPath.row-1) )
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewStaty") as! ViewController
vc.obr = f.3
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
Views are loaded only once in the lifetime of a view controller, so viewDidLoad is only run once.
One way to do this is to reload the data in viewWillAppear which is fired when the view appears, but this might run many times.
Another way is to have a delegate method of vc2 that is implemented by vc1. This delegate method is run when the data is changed in vc2 and since vc1 implements the delegate, it can then choose to reload the view.
Yet another way, and one that I prefer, is to use something like Core Data as a model. That way when vc2 changes the data, vc1 can be observing the state of objects it is interested in and react to changes in the model through the NSFetchedResultsControllerDelegate methods.
You could choose to use Realm as a persistence mechanism, and I'm sure there is a similar way to observe the model and react to changes.
inn order to reloadData in background thread, you need to use
DispatchQueue.main.async { self.collectionView.reloadData() }
implement your vc from
UICollectionViewDelegate , UICollectionViewDataSource
then in
viewDidLoad()
self.collectionView.delegate = self
self.collectionView.dataSource = self
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.
I am new to iOS development. I want to create collectionView in two different view controllers with same UI.I want to create only one UICollectionView and resuse it on different view controller instead of create separate collectionViews . On approach i can follow is to create the UICollectionViewController subclass and add this on my viewcontrollers as a childviewcontroller, but not sure if this is the correct approach do not know how addChildViewcontroller works and how to pass data between child and parent viewcontrollers. It would be great if someone can help on this. If any sample code is available to achive this please let me know.
Any help is much appreciated.
You can pass around the same collection view controller instance. Add it in viewWillAppear and remove in viewDidDisappear in first and second classes. Here is a sample code that you could use,
extension UIColor {
class func randomColor() -> UIColor {
let red = CGFloat(arc4random_uniform(255)) / 255.0
let green = CGFloat(arc4random_uniform(255)) / 255.0
let blue = CGFloat(arc4random_uniform(255)) / 255.0
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
}
class MyCollectionViewController: UICollectionViewController {
let data: [UIColor]
init(data: [UIColor]) {
self.data = data
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSizeMake(100, 100)
layout.scrollDirection = UICollectionViewScrollDirection.Vertical
super.init(collectionViewLayout: layout)
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath)
cell.backgroundColor = data[indexPath.item]
return cell
}
}
class FirstViewController: UIViewController {
lazy var myData:[UIColor] = {
var allData = [UIColor]()
for i in 0 ..< 20 {
allData.append(UIColor.randomColor())
}
return allData
}()
var collectionViewController: MyCollectionViewController!
override func viewDidLoad() {
super.viewDidLoad()
collectionViewController = MyCollectionViewController(data: self.myData)
let barButton = UIBarButtonItem(title: "Show next", style: .Plain, target: self, action: "showNext:")
navigationItem.rightBarButtonItem = barButton
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let collectionView = collectionViewController.view
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
addChildViewController(collectionViewController)
collectionView.topAnchor.constraintEqualToAnchor(view.topAnchor).active = true
collectionView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
collectionView.leftAnchor.constraintEqualToAnchor(view.leftAnchor).active = true
collectionView.rightAnchor.constraintEqualToAnchor(view.rightAnchor).active = true
collectionViewController.didMoveToParentViewController(self)
}
override func viewWillDisappear(animated: Bool) {
super.viewDidDisappear(animated)
collectionViewController.willMoveToParentViewController(nil)
collectionViewController.view.removeFromSuperview()
collectionViewController.removeFromParentViewController()
}
func showNext(sender: AnyObject) {
let secondViewController = SecondViewController(collectionViewController: collectionViewController)
navigationController?.pushViewController(secondViewController, animated: true)
}
}
class SecondViewController: UIViewController {
var collectionViewController: MyCollectionViewController!
init(collectionViewController: MyCollectionViewController) {
self.collectionViewController = collectionViewController
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let collectionView = collectionViewController.view
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
addChildViewController(collectionViewController)
collectionView.topAnchor.constraintEqualToAnchor(view.topAnchor).active = true
collectionView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
collectionView.leftAnchor.constraintEqualToAnchor(view.leftAnchor).active = true
collectionView.rightAnchor.constraintEqualToAnchor(view.rightAnchor).active = true
collectionViewController.didMoveToParentViewController(self)
}
override func viewWillDisappear(animated: Bool) {
super.viewDidDisappear(animated)
collectionViewController.willMoveToParentViewController(nil)
collectionViewController.view.removeFromSuperview()
collectionViewController.removeFromParentViewController()
}
}
I have a Set of Answers you can use,
My source Code is ParentViewController and ChildViewController are
same viewController to be declared.
First you create the ParentViewController and add the
UICollectionView then set the Cell size in ParentViewController.
Second you create UICollectionViewCell in same parentViewController,
then add what u need Label or Buttons to be declare.
In ParentViewController class declare 'UICollectionViewDelegate',
Ex: class MyViewController: UIViewController, UICollectionViewDelegate
Then Create UICollectionViewDelegate methods and i have put my
methods below,
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayvalue.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseIdentifier", forIndexPath: indexPath)
// Configure the cell
let baseView = cell.viewWithTag(101)
let titleLabel = baseView?.viewWithTag(102) as! UILabel
titleLabel.text = arrayvalue[indexPath.row] as String
return cell
}
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(CellSize)
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
collectionView.deselectItemAtIndexPath(indexPath, animated:true)
let storyBoard = UIStoryboard(name: "storyboardName", bundle: nil)
let name: classname = storyBoard.instantiateViewControllerWithIdentifier("reuseIdentifier") as! AnotherViewController
self.navigationController?.pushViewController(name, animated: true)
}
Very Important to give storyboard 'reuseIdentifier' value and also
give inside the class cellForItemAtIndexPath reuseIdentifier, example this line
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseIdentifier", forIndexPath: indexPath)
This code was working for me...