Custom menu bar with collectionview - ios

I want to ask. I make a custom menubar from collectionView and I want to link and change my menubar of collection view and the data from another collectionView while swiping. Here a picture what I'm try to make
but while try to swipe to left and right my menu bar is not following, I already make a reference to capture the value. but it's still not work. here is my code
class SegmentedView: UIView {
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
let knowledge = ["Pengetahuan", "Keterampilan", "Sikap"]
var selectedMenu: CGFloat?
}
extension SegmentedView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return knowledge.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! SegmentedCell
let item = knowledge[indexPath.item]
cell.nameLabel.text = item
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedMenu = CGFloat(indexPath.item) * frame.width
}
}
// This from my SegmentedViewController
class SegmentedViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var segmentedViews: SegmentedView!
let cellId = "assesmentCell"
let colors: [UIColor] = [UIColor.blue, UIColor.yellow, UIColor.green]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupCollection()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x / 3)
segmentedViews.selectedMenu = scrollView.contentOffset.x / 3
}
func setupCollection() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isPagingEnabled = true
}
}
thank you.

Your selectedMenu property does not listen for an event with the didSet property observer. So when you set selectedMenu in scrollViewDidScroll, nothing happens.
It may be a solution:
var selectedMenu: CGFloat? {
didSet {
collectionView.selectItem(at: <#T##IndexPath?#>, animated: <#T##Bool#>, scrollPosition: <#T##UICollectionView.ScrollPosition#>)
}
}
But be careful with the collectionView(_:didSelectItemAt:) method of SegmentedView, it can bring some unwanted behaviors. You can use a delegate pattern to trigger the method in another class.

Related

Horizontal direction UICollectionView with UIRefreshControl reloadData() not working

I need to implement horizontal UICollectionView.
Sometimes, I need to update the data, So I decided to implement a refreshcontrol.
I called reloadData() with refreshcontrol. It seems to be working, but cellForItemAt is not called the correct time.
So, when the cell count has changed it will be crashed.
class ViewController: UIViewController, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
private let refreshControl = UIRefreshControl()
private var cellCount = 1
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: .valueChanged)
collectionView.register(UINib(nibName: "TopCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal // not working
// layout.scrollDirection = .vertical //working
}
}
#objc func refresh(sender: UIRefreshControl) {
cellCount = cellCount + 1
collectionView.reloadData()
sender.endRefreshing()
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellCount
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: TopCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! TopCollectionViewCell
return cell
}
}
If the direction change to vertical, it is working.
Is it correct behavior? or just a bug?
Set the alwaysBounceVertical property of collectionView as true in viewDidLoad(), i.e.
collectionView.alwaysBounceVertical = true

Swift: How to reuse a ViewController properly

I got a HomeController of type UICollectionViewController which handles some PictureCells (contains a picture and a label).
Now I am sending one PictureCell to another ViewController to edit the label. This all works perfectly. I could send it back to the HVC using a protocol but instead of going back I want to go one step further and Display the edited PictureCell in a new ViewController.
Instead of creating a completely new one, I am subclassing the existing HomeController to reuse his CollectionView. All works fine and the view shows up but the edited PictureCell is not showing up at all, even tho I can show it's layer, the Cell itself with its content doesn't show up.
(Messy) Class-Diagram looks like this:
ClassDiagram
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, MyProtocol {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.black
collectionView?.register(PictureCell.self, forCellWithReuseIdentifier: Constants.cellId)
}
//MARK: Get value from second VC
var valueSentFromSecondViewController: String?
var cellSentFromSecondViewController: PictureCell?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
//Acting with Protocol here
}
//HERE ARE THE COLLECTIONVIEWFUNCTIONS
}
class PictureEditViewController: UIViewController, UITextFieldDelegate {
var delegate:MyProtocol?
var myCell: PictureCell?
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
init(pictureCellInit: PictureCell?) {
self.myCell = pictureCellInit
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
showThings()
}
#objc func showNextVC() {
let newViewController = PictureShowViewController(withCell: self.myCell)
newViewController.modalPresentationStyle = .overCurrentContext
present(newViewController, animated: true) //with dismiss
}
#objc func showThings() {
view.addSubview(collectionView)
self.collectionView.frame = CGRect(x: x, y: y, width: width, height: height)
self.setupViews()
}
func setupViews() {
//ADDING THE SUBVIEWS
}
func confBounds() {
//LAYOUT
}
#objc func handleDismiss() {
self.dismiss(animated: true, completion: nil)
}
class PictureShowViewController: HomeController {
//MARK: Variable/Constant declaration
var myCellToShow: PictureCell?
init(withCell: PictureCell?) {
let layoutUsing = UICollectionViewFlowLayout()
myCellToShow = withCell
super.init(collectionViewLayout: layoutUsing)
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
collectionView?.addSubview(myCellToShow!)
self.collectionView?.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = myCellToShow {
cell.layer.borderColor = UIColor.red.cgColor
cell.layer.borderWidth = 2
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.cellId, for: indexPath) as! PictureCell
return cell
}
}
//Size of Cell
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellSize = CGFloat(view.frame.width)
return CGSize(width: cellSize, height: cellSize)
}
}
Does anyone have an idea where I went wrong?
It is a bad idea to use PictureCell to move your data around view controllers. UICollectionView reuses instances of cells so the content of the cell is bound to change anytime.
So instead of using your cell, hand over the underlying data and insert the data in to the newly dequeued collection view cell.
In the end,
var cellSentFromSecondViewController: PictureCell?
should be var pictureFromSecondViewController: YourPictureData
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.cellId, for: indexPath) as! PictureCell
//Set your data
cell.picture = pictureFromSecondViewController
return cell
}

How to avoid collection view flickering effect within tableview when changing flowlayouts and provide a smoother transition?

I have a collectionview embedded within a tableview. The collectionview can have either a vertical or horizontal flowlayout. When the user makes an action, the tableviewcell height changes from 200 to full tableview height and the collection view changes flowlayout. However, the collectionView flickers since there are not enough cells loaded yet when performing the layout change from horizontal to vertical. Also, when reverting to the horizontal flowLayout, there is no transition at all. Not sure why the transition occurs when changing flowLayout from horizontal to vertical but not vertical to horiztontal. How can I avoid the flicker when changing the layout and continue to utilize Apple's transition animation when reverting to the horizontal layout?
GitHub
class ViewController: UIViewController {
// If horizontal layout, then display 3 rows (with heights 200)
// If veritcal layout, then display 1 row (with height of full tableview height)
var isHorizontal = true
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
#IBAction func barButtonTapped(_ sender: UIBarButtonItem) {
isHorizontal = !isHorizontal
sender.title = isHorizontal ? "Vertical" : "Horizontal"
tableView.reloadData()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isHorizontal ? 3 : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EmbeddedTableViewCell", for: indexPath) as! EmbeddedTableViewCell
cell.setup(isHorizontal: isHorizontal)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return isHorizontal ? 200 : tableView.frame.height
}
}
class EmbeddedTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionView.dataSource = self
collectionView.delegate = self
}
}
fileprivate let horizontalFlowLayout: UICollectionViewFlowLayout = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 10
flowLayout.minimumLineSpacing = 10
flowLayout.scrollDirection = .horizontal
return flowLayout
}()
fileprivate let verticalFlowLayout: UICollectionViewFlowLayout = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 10
flowLayout.minimumLineSpacing = 10
flowLayout.scrollDirection = .vertical
return flowLayout
}()
func setup(isHorizontal: Bool) {
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
if isHorizontal && flowLayout.scrollDirection != .horizontal {
collectionView.setCollectionViewLayout(horizontalFlowLayout, animated: true)
} else if !isHorizontal && flowLayout.scrollDirection != .vertical {
collectionView.setCollectionViewLayout(verticalFlowLayout, animated: true)
}
}
}
extension EmbeddedTableViewCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SimpleCollectionViewCell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: 150)
}
}

Gesture recognizer doesn't work on UICollectionView inside of other UICollectionViewCell

I created a UICollectionView as a major UICollectionView and each collectionViewCell has another UICollectionView "B" as it's subview
Then I add a tapGesture in "B". but "B" can't trigger this gesture.
Following is my main code:
Main UICollectionView
import Foundation
import UIKit
class mainViewController :UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var mainCollectionView: UICollectionView!
override func viewDidLoad() {
self.mainCollectionView.registerNib(UINib(nibName: "OuterCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "OuterCollectionViewCell")
self.mainCollectionView.delegate = self
self.mainCollectionView.dataSource = self
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("OuterCollectionViewCell", forIndexPath: indexPath) as! OuterCollectionViewCell
return cell
}
}
UICollectionView B:
class OuterCollectionViewCell: UICollectionViewCell,UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var InnerCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
InnerCollectionView.registerNib(UINib(nibName: "InnerCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CellInner")
}
func handleTapGesture(sender: UITapGestureRecognizer) {
print("tapguesture")
}
override func didMoveToWindow(){
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTapGesture:")
InnerCollectionView.backgroundColor = UIColor.redColor()
InnerCollectionView.addGestureRecognizer(tapGesture)
InnerCollectionView.delegate = self
InnerCollectionView.dataSource = self
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CellInner", forIndexPath: indexPath) as! InnerCollectionViewCell
return cell
}
}
This issue is bugging me an whole day. Is there anyone who has any ideas about how to fix this issue?
I have uploaded the project into github: https://github.com/dengchicheng/Test2
update------------------------------
today I init the innerCollectionView by code programmably instead of init by NIB. and it works
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = UICollectionViewScrollDirection.Horizontal
let InnerCollectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 800, height: 600), collectionViewLayout: flowLayout)
I don't know what the NIB did to the UIcollectionview
I did set the userInteractive to be true in the interface builder

UICollectionView in a UICollectionViewCell

I am interested in having a collectionview as a part of a collection view cell but for some reason cannot figure out how this would be done. Where would I implement the necessary methods for the cells collectionview?
There's an article that Ash Furrow wrote that explains how to put an UICollectionView inside an UITableViewCell. It's basically the same idea when using it inside an UICollectionViewCell.
Everything is done programatically. No storyboards.
I added a UICollectionView inside my UICollectionViewCell. I also show how to add again a UICollectionViewCell inside the created UICollectionView to have this result
import UIKit
class CategoryCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
private let cellId = "cell"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
return collectionView
}()
func setupViews() {
backgroundColor = .blue
addSubview(appsCollectionView)
appsCollectionView.delegate = self
appsCollectionView.dataSource = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)
addConstrainstWithFormat("H:|-8-[v0]-8-|", views: appsCollectionView)
addConstrainstWithFormat("V:|[v0]|", views: appsCollectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
}
class AppCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews(){
backgroundColor = .red
}
}
My UICollectionViewController
import UIKit
class FeaturedAppsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
collectionView?.backgroundColor = .white
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(view.frame.width, 150)
}
}
The whole explanation can be found and was developed by "Let´s build that app": https://www.youtube.com/watch?v=Ko9oNhlTwH0&list=PL0dzCUj1L5JEXct3-OV6itP7Kz3tRDmma
This is too late for this answer but it might help others. This is an example of UICollectionView inside a UICollectionViewCell.
Lets start by having a mainCollectionView. Then on each cell of this collection create and initialize a new UICollectionView and right place to do that is in this following delegate of UICollectionView
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath)
e.g I initialize the MainCollectionViewCell here and then MainCollectionViewCell handles the logic to create a new UICollectionView
guard let collectionViewCell = cell as? MainCollectionViewCell else { return }
collectionViewCell.delegate = self
let dataProvider = ChildCollectionViewDataSource()
dataProvider.data = data[indexPath.row] as NSArray
let delegate = ChildCollectionViewDelegate()
collectionViewCell.initializeCollectionViewWithDataSource(dataProvider, delegate: delegate, forRow: indexPath.row)
collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
Here is the initializer on MainCollectionViewCell that creates a new UICollectionView
func initializeCollectionViewWithDataSource<D: protocol<UICollectionViewDataSource>,E: protocol<UICollectionViewDelegate>>(dataSource: D, delegate :E, forRow row: Int) {
self.collectionViewDataSource = dataSource
self.collectionViewDelegate = delegate
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .Horizontal
let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: flowLayout)
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseChildCollectionViewCellIdentifier)
collectionView.backgroundColor = UIColor.whiteColor()
collectionView.dataSource = self.collectionViewDataSource
collectionView.delegate = self.collectionViewDelegate
collectionView.tag = row
self.addSubview(collectionView)
self.collectionView = collectionView
collectionView.reloadData()
}
Hope that helps !!
I did an example for this and put in on github. It demonstrates the use UICollectionView inside a UICollectionViewCell.
https://github.com/irfanlone/Collection-View-in-a-collection-view-cell
Easiest solution for collectionview inside collectionview using storyboard and Swift 5
Please refer this link for nested collectionview example
import UIKit
class ParentViewController:UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Declare variable
//MARK: Decalare outlet
#IBOutlet weak var outerCollectionView: UICollectionView!
#IBOutlet weak var pageControl: UIPageControl!
let outerCount = 4
//MARK: Decalare life cycle methods
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = outerCount
}
//MARK: Collection View delegate methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return outerCount
}
//MARK: Collection View datasource methods
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OuterCell", for: indexPath) as! OuterCollectionViewCell
cell.contentView.backgroundColor = .none
return cell
}
//MARK:- For Display the page number in page controll of collection view Cell
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleRect = CGRect(origin: self.outerCollectionView.contentOffset, size: self.outerCollectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
if let visibleIndexPath = self.outerCollectionView.indexPathForItem(at: visiblePoint) {
self.pageControl.currentPage = visibleIndexPath.row
}
}
}
class OuterCollectionViewCell: UICollectionViewCell ,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var InnerCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
InnerCollectionView.delegate = self
InnerCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InnerCell", for: indexPath) as! InnerCollectionViewCell
cell.contentView.backgroundColor = .green
return cell
}
}
class InnerCollectionViewCell: UICollectionViewCell{
}

Resources