I'm really new in iOS/Swift and i'm in a small project. In this project i have a UITableView inside ViewController. And i have another file custom CollectionViewCell in side UITableViewCell.
I want when user click a cell in collectionview it will open another ViewController and it get data from this collectionviewcell.
This is my uitableview swift file:
class IndexRow: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var names:[String] = ["Movie 1","Movie 2","Movie 3","Movie 4","Movie 5","Movie 6"]
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
collectionView.registerClass(indexOneMovie.self, forCellWithReuseIdentifier: "onemovie")
let nib = UINib(nibName: "indexOneMovie",bundle: nil)
collectionView.registerNib(nib, forCellWithReuseIdentifier: "onemovie")
self.collectionView.backgroundColor = UIColor.clearColor()
self.collectionView.delegate = self
self.collectionView.dataSource = self
print("Hello")
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return names.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("onemovie", forIndexPath: indexPath) as! indexOneMovie
cell.poster.image = UIImage(named: "poster.jpg")
cell.name.text = names[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(indexPath.item)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let itemsPerRow:CGFloat = 2
let hardCodedPadding:CGFloat = 0
let itemWidth = (collectionView.bounds.width / itemsPerRow) - hardCodedPadding
let itemHeight = collectionView.bounds.height - (hardCodedPadding)
return CGSize(width: itemWidth, height: itemHeight)
}
How i can do it?
ok i have recently implemented the same in my app these are the links where i refered initially -
https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
http://www.thorntech.com/2015/08/want-your-swift-app-to-scroll-in-two-directions-like-netflix-heres-how/
you are making uicollectionview delegate confirms to uitableview cell so you cannot present or push to other view controller.
here is my code hope it will help you
homeController.swift which contains uitableview
extension HomeController : UITableViewDelegate {
func tableView(tableView: UITableView,willDisplayCell cell: UITableViewCell,forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
//here setting the uitableview cell contains collectionview delgate conform to viewcontroller
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row, andForSection: indexPath.section)
tableViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
func tableView(tableView: UITableView,didEndDisplayingCell cell: UITableViewCell,forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
storedOffsets[indexPath.row] = tableViewCell.collectionViewOffset
}
}
extension HomeController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView,numberOfItemsInSection section: Int) -> Int {
let element : [CollectionViewElement] = self.returnCollectionViewElementAccordingToIndex(collectionView.tag)
return element.count
}
func collectionView(collectionView: UICollectionView,cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",forIndexPath: indexPath) as! horizontalCollectionViewCell
let element : [CollectionViewElement] = self.returnCollectionViewElementAccordingToIndex(collectionView.tag)
cell.cellTitleLabel.text = element[indexPath.row].videos.title
cell.cellGenerLabel.text = element[indexPath.row].videos.gener
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
print("collectionviewtag:\(collectionView.tag) + indexpathrow:\(indexPath.row)")
//from here you can do push or present to anyview controller
// collectionviewtag is tableView cell row value and indexpathrow return collectionView cell row value.
}
}
TableViewCell.swift :custom UITableViewCell which contains collectionView
class TableViewCell: UITableViewCell {
#IBOutlet private weak var collectionView: UICollectionView!
#IBOutlet weak var cellLabel: UILabel!
#IBOutlet weak var cellButton: UIButton!
#IBAction func CellButtonActionTry(sender: UIButton) {
print("Dude \(cellButton.tag)")
}
var collectionViewOffset: CGFloat {
get {
return collectionView.contentOffset.x
}
set {
collectionView.contentOffset.x = newValue
}
}
func setCollectionViewDataSourceDelegate<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>
(dataSourceDelegate: D, forRow row: Int , andForSection section : Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row // tableView indexpathrow equals cell tag
collectionView.reloadData()
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
You can do it like this :
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(indexPath.item)
let name = names[indexPath.item]
let distinationViewController = DistinationViewController()
distinationViewController.name = name
if let navVC: UINavigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
navVC.pushViewController(distinationViewController, animated: true)
}
}
This is just a way to do it i dont know which view you want to push or what your names array contains so kindly change those things accordingly.
get root navigationcontroller from uiapplication and perform push on it.
Related
I have an issue with a collectionView inside a TableVieCell. When i tap on a collectionCell, didSelectItemAt doesn´t get called. I have a button in the collectionCell so i tried to disable the user interaction and enable the contentView userInteraction but it didn't work.
The red rectangle is the tableCell and the blue rectangle is the collecionView insede the table view cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MedCellWithCollection") as? MedCellWithCollection{
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor.white.withAlphaComponent(0.0)
cell.selectedBackgroundView = backgroundView
cell.setMedName(name: self.medCatalog[indexPath.row].nombre, uso: self.medCatalog[indexPath.row].uso , array: self.medCatalog[indexPath.row].enfermedades[0].aplicaciones , index: indexPath.row)
cell.layoutIfNeeded()
cell.layoutSubviews()
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
return cell
}
}
return UITableViewCell()
}
The tableCell
class MedCellWithCollection: UITableViewCell {
//Outlets
#IBOutlet weak var medText: UILabel!
#IBOutlet weak var uso: UILabel!
#IBOutlet weak var arrowIcon: UIImageView!
#IBOutlet weak var CollectionView: UICollectionView!
//Variables
var dosesType:[Aplicacion]?
override func awakeFromNib() {
super.awakeFromNib()
self.collectionViewSetUp()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setMedName(name: String, uso: String, array: [Aplicacion], index: Int){
self.medText.text = name
self.uso.text = uso
self.dosesType = array
self.CollectionView.reloadData()
}
}
extension MedCellWithCollection: UICollectionViewDataSource, UICollectionViewDelegate{
func collectionViewSetUp(){
self.CollectionView.delegate = self
self.CollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dosesType?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "doseColletion", for: indexPath as IndexPath) as? DoseCollection {
cell.setButtonConfig(doseType: self.dosesType![indexPath.row].metodo , index: indexPath.row)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("tapped")
}
}
CollectionCell
import UIKit
class DoseCollection: UICollectionViewCell {
//Outlets
#IBOutlet weak var Button: Button!
//Variables
let constants = Constants()
func setButtonConfig(doseType: String, index: Int){
self.Button.titleLabel?.text = doseType
self.Button.backgroundColor = constants.COLOR_ARRAY[index]
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
}
}
Potential Solutions:
1) Should be Single Selection for tableView selection property, programmatically it can be done by tableView.allowsSelection = true
2) The class is not the UITableViewDelegate for that table view, though UITableViewController is supposed to set that automatically.
tableView?.delegate = self
3) If the problem arise with UITapGestureRecognizer:
let tap = UITapGestureRecognizer(target: self, action:Selector("dismissKeyboard"))
view.addGestureRecognizer(tap)
tap.cancelsTouchesInView = false
BOL :)
In my case, I want to change the background of the button in other words the background of the cell in the collection view:
class CustomCVCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
grayBackgroundViewWithImage.image =
isSelected ? UIImage(named: "") : UIImage()
}
}
In the main class where the collection view is stored create this variable:
class CustomViewController: UIViewController {
///save the indexPath of last selected cell
private var lastSelectedIndexPath: IndexPath? }
In viewDidLoad() set this value to false:
customCollectionView.allowsMultipleSelection = false
Further code in data source. In my case, the first cell should be is selected:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCVCell.cellID(),
for: indexPath) as! CustomCVCell
if indexPath.row == 0 {
lastSelectedIndexPath = indexPath
cell.isSelected = true
}
//update last select state from lastSelectedIndexPath
cell.isSelected = (lastSelectedIndexPath == indexPath)
return cell
}
Further code in the delegate:
///UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard lastSelectedIndexPath != indexPath else { return }
if let index = lastSelectedIndexPath {
let cell = collectionView.cellForItem(at: index) as! CustomCVCell
cell.isSelected = false
}
let cell = collectionView.cellForItem(at: indexPath) as! CustomCVCell
cell.isSelected = true
lastSelectedIndexPath = indexPath
}
The current structure is
firstViewController -> pushViewController(SecondViewController)
-UIViewController ->filename : secondViewController
-UITableView
-UITableViewCell (custom cell) ->filename : secondTableViewCell
-UICollectionView
-UICollectionViewCell (custom cell) ->filename : secondCollectionViewCell
When 'didSelectItemAt' is done in the 'UICollectionView'
I would like to call the 'popviewcontroller' from UIViewController.
And I want to pass the data on the selected item to firstViewController.
but i don't know how
** ViewController **
struct cellStat{
var opened = Bool()
var title = String()
var sectionData = [ItemModel]()
}
//선물 카테고리 리스트 보여주는 모달
class GiftCategoryModalVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var categoryTitleModels: [ItemModel] = []
private var giftItemModels: [ItemModel] = []
private var titleCellData: [cellStat] = []
//LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: GiftCategoryTBCell.reusableIdentifier, bundle: nil), forCellReuseIdentifier: GiftCategoryTBCell.reusableIdentifier)
tableView.register(UINib(nibName: GiftCategoryListTBCell.reusableIdentifier, bundle: nil), forCellReuseIdentifier: GiftCategoryListTBCell.reusableIdentifier)
//get gift categofy title
GiftPageAPIService.shared.selectCode(code: "CD005") { (itemModels) in
self.categoryTitleModels = itemModels
itemModels.forEach { (itemModel) in
self.titleCellData.append(cellStat(opened: false, title: itemModel.codeNm!, sectionData: []))
}
self.tableView.reloadData()
}
setupLayout()
}
//LAYOUT
fileprivate func setupLayout(){
setNavbar()
}
fileprivate func setNavbar(){
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "icon_arrow_left"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(handleEndButton))
self.navigationController?.navigationBar.barTintColor = UIColor.init(hex: 0xececec)
self.navigationController?.navigationBar.tintColor = UIColor.init(hex: 0x979797)
}
//MAKR:- ACTION
#objc func handleEndButton(){
self.navigationController?.popViewController(animated: true)
}
}
extension GiftCategoryModalVC: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if titleCellData[section].opened == true{
return 2
}else{
return 1
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return titleCellData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: GiftCategoryTBCell.reusableIdentifier, for: indexPath) as! GiftCategoryTBCell
cell.setCategotyNm = categoryTitleModels[indexPath.section].codeNm
if titleCellData[indexPath.section].opened == true{
cell.setImgView = UIImage(named: "arrow_up")
}else{
cell.setImgView = UIImage(named: "arrow_down")
}
return cell
}else{
//this cell is call tableCell
let cell = tableView.dequeueReusableCell(withIdentifier: GiftCategoryListTBCell.reusableIdentifier, for: indexPath) as! GiftCategoryListTBCell
cell.setItems = titleCellData[indexPath.section].sectionData
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
if titleCellData[indexPath.section].opened == true{
titleCellData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .automatic)
}else{
titleCellData[indexPath.section].opened = true
GiftPageAPIService.shared.selectCode(code: categoryTitleModels[indexPath.section].codeCd!) { (items) in
self.titleCellData[indexPath.section].sectionData = items
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .automatic)
}
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0{
return 50
}else{
let cellHeight: Int = (titleCellData[indexPath.section].sectionData.count + 2) / 3
return self.view.frame.height * 0.24 * CGFloat(cellHeight)
}
}
}
** TableViewCell **
class GiftCategoryListTBCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
var items: [ItemModel]?
var category: ItemModel?
var setCategory: ItemModel?{
didSet{
self.category = setCategory!
getCategoryData()
}
}
var setItems: [ItemModel]?{
didSet{
self.items = setItems!
collectionView.reloadData()
}
}
let flowlayout = UICollectionViewFlowLayout()
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: GiftItemCVCell.reusableIdentifier, bundle: nil), forCellWithReuseIdentifier: GiftItemCVCell.reusableIdentifier)
setupLayout()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//MARK:- LAYOUT
fileprivate func setupLayout(){
collectionView.backgroundColor = UIColor.init(hex: 0xf7f7f7)
collectionView.isScrollEnabled = false
}
fileprivate func getCategoryData(){
GiftPageAPIService.shared.selectCode(code: (category?.codeCd)!) { (items) in
self.items = items
self.collectionView.reloadData()
}
}
}
extension GiftCategoryListTBCell: UICollectionViewDataSource,UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if items == nil{
return 0
}else{
return (items?.count)!
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GiftItemCVCell.reusableIdentifier, for: indexPath) as! GiftItemCVCell
cell.setItem = items?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
/*
I tried to do the work here.
*/
}
}
extension GiftCategoryListTBCell: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: contentView.frame.width * 0.07, left: contentView.frame.width * 0.07, bottom: contentView.frame.width * 0.07, right: contentView.frame.width * 0.07)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return contentView.frame.width * 0.03
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (contentView.frame.width) * 0.25
return CGSize(width: width, height: width * 1.5)
}
}
try this :
let window = UIApplication.shared.keyWindow
window?.topMostWindowController?.navigationController?.popViewController(animated: true)
You mean something like this?
Create a protocol in the tableviewcell file
class myTableCell: UITableViewCell{
var myString = "mystring"
var delegate = myNewDelegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.didSelect(text: self.myString)
}
}
protocol myNewDelegate {
func didSelect(text: String)
}
This goes in the view controller class that the table cell is in.
class MyTableViewController: UITableViewController, myNewDelegate{
var stringToPass = String()
func didSelectText(text: String){
stringToPass = text
self.performSegue(withIdentifier: "editProfileSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editProfileSegue"{
let viewcontroller = segue.destination as! FirstViewController
viewcontroller.myPassedString = self.stringToPass
}
}
}
And then id the receiving class
class FirstViewController: UIViewController{
var myPassedString = String()
}
I understand what your mean.
If you want to pop your controller to previous controller in tablecell just to get its viewcontroller which control the cell and try to pop and if you try this method, why not override delegate func in your viewcontroller ?May a little bit hard to read the code, but useful, and you can use
self.navigationcontroller?.popViewController
If you have already use the viewcontroller to delegate the other tableview, just to judge the tableview name in your delegate func
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == blah blah
{
do what you want
}
}
And if you need to pass the data between the different viewController especially pass the data to pre-viewController you need to write delegate, or need to __block callback.
I have the structure like this:
UITableView -> UITableViewCell -> UICollectionView ->
UICollectionViewCell
So what I’m trying to achieve is that I want to make UICollectionViews in UITableViewCells to scroll synchronised. For example when you scroll manually the first UICollectionView on the first row, I want the rest of UICollectionViews to follow, but the Text Labels to stay in the same position all the time. (Please see the image below)
EDIT: I know that I have to use contentOffset somehow, but don’t know how to implement in this case scenario. Any help would be appreciated.
Click to see the image
Click to see the gif
Okay I managed to get this working, Please keep in mind the code is just for the question purposes and contains lot of non-generic parameters and force casting that should be avoided at any cost.
The class for MainViewController containing the tableView:
protocol TheDelegate: class {
func didScroll(to position: CGFloat)
}
class ViewController: UIViewController, TheDelegate {
func didScroll(to position: CGFloat) {
for cell in tableView.visibleCells as! [TableViewCell] {
(cell.collectionView as UIScrollView).contentOffset.x = position
}
}
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as? TableViewCell else { return UITableViewCell() }
cell.scrollDelegate = self
return cell
}
}
The class for your tableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var collectionView: UICollectionView!
weak var scrollDelegate: TheDelegate?
override func awakeFromNib() {
super.awakeFromNib()
(collectionView as UIScrollView).delegate = self
collectionView.dataSource = self
}
}
extension TableViewCell: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! CollectionViewCell
cell.imageView.image = #imageLiteral(resourceName: "litecoin.png")
return cell
}
}
extension TableViewCell: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollDelegate?.didScroll(to: scrollView.contentOffset.x)
}
}
The class for the collectionViewCell is irelevant since it's just implementation detail. I will post this solution to github in a second.
Disclaimer: This works just for visible cells. You need to implement the current scroll state for the cells ready for reuse as well. I will extend the code on github.
I came up with a working solution you can test on a playground:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyCollectionCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate {
var originatingChange: Bool = false
var observationToken: NSKeyValueObservation!
var offsetSynchroniser: OffsetSynchroniser? {
didSet {
guard let offsetSynchroniser = offsetSynchroniser else { return }
collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
observationToken = offsetSynchroniser.observe(\.currentOffset) { (_, _) in
guard !self.originatingChange else { return }
self.collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
}
}
}
lazy var collection: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 40, height: 40)
layout.scrollDirection = .horizontal
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = .white
collection.dataSource = self
collection.delegate = self
collection.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
return collection
}()
override func layoutSubviews() {
super.layoutSubviews()
collection.frame = contentView.bounds
contentView.addSubview(collection)
}
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: "cell", for: indexPath)
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.backgroundColor = .white
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
originatingChange = true
offsetSynchroniser?.currentOffset = scrollView.contentOffset
originatingChange = false
}
}
class OffsetSynchroniser: NSObject {
#objc dynamic var currentOffset: CGPoint = .zero
}
class MyViewController : UIViewController, UITableViewDataSource {
var tableView: UITableView!
let offsetSynchroniser = OffsetSynchroniser()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
tableView = UITableView(frame: .zero, style: .plain)
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(tableView)
tableView.dataSource = self
tableView.register(MyCollectionCell.self, forCellReuseIdentifier: "cell")
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCollectionCell
cell.selectionStyle = .none
cell.collection.reloadData()
cell.offsetSynchroniser = offsetSynchroniser
return cell
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
To make it work with a playground you will see a lot of code that if you are using storyboards or xib is not needed. I hope anyway that the base idea is clear.
Explanation
Basically I created an object called OffsetSynchroniser which has an observable property called currentOffset. Each cell of the tableView accepts an offsetSynchroniser and on didSet they register with KVO for notifications of currentOffset changes.
Each cells also registers to its own collection's delegate and implements the didScroll delegate method.
When any of those collectionView causes this method to be triggered the currentOffset var of the synchroniser is changed and all the cells that are subscribed through KVO will react to the changes.
The Observable object is very simple:
class OffsetSynchroniser: NSObject {
#objc dynamic var currentOffset: CGPoint = .zero
}
then your tableViewCell will have an instance of this object type and on didSet will register with KVO to the var currentOffset:
var originatingChange: Bool = false
var observationToken: NSKeyValueObservation!
var offsetSynchroniser: OffsetSynchroniser? {
didSet {
guard let offsetSynchroniser = offsetSynchroniser else { return }
collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
observationToken = offsetSynchroniser.observe(\.currentOffset) { (_, _) in
guard !self.originatingChange else { return }
self.collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
}
}
}
The originatingChange variable is to avoid that the collectionView that is actually initiating the offset change will react by causing the offset to be re-set twice.
Finally, always in your TableViewCell, after registering itself as collectionViewDelegate you will implement the method for didScroll
func scrollViewDidScroll(_ scrollView: UIScrollView) {
originatingChange = true
offsetSynchroniser?.currentOffset = scrollView.contentOffset
originatingChange = false
}
In here we can change the currentOffset of the synchroniser.
The tableViewController will at this point just have the ownership for the synchroniser
class YourTableViewController: UItableViewController { // or whatever ViewController contains an UITableView
let offsetSynchroniser = OffsetSynchroniser()
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCollectionCell
...
cell.offsetSynchroniser = offsetSynchroniser
return cell
}
}
The best way I can think of off the top of my head to do something like this would be to store all of your collectionViews in a collection object. You can then use the UIScrollView's scrollViewDidScroll delegate method from those collectionViews. Just make sure you have your delegate set correctly.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
for view in collectionViewCollection where view.scrollView != scrollView{
view.scrollView.contentOffset = scrollView.contentOffset
}
}
This is untested code, so not a complete answer but it should get you started.
I have a custom UITableViewCell class which has a collectionview inside. In this class there's a varaible "nbElements" which contain the number of collectionview cells to display. I want to know how I can initialize the value of nbElements when I create a tableview cell with dequeueReusableCell?
Here is the code of the custom tableview cell:
class CustomTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
var nbElements = Int!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.collectionView.register(UINib(nibName: "CustomCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "customCollectionViewCell")
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return nbElements
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCollectionViewCell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = (self.collectionView.frame.width / 2.0) - 1
let size = CGSize.init(width: cellWidth, height: cellWidth)
return size;
}
}
Here is the code when I create a tableview cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customTableViewCell", for: indexPath) as! CustomTableViewCell
cell.nbElements = 4
return cell
}
Regards.
Trigger reload of collectionView whenever the nbElements is set.
var nbElements = Int! {
didSet {
collectionView.reloadData()
}
}
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