So I have tableview embedded in collectionview.
I have xib for tableview.
When user select a cell of tableview I want to navigate to another view controller.
I tried this method but its not working
let storyboardId = "Login"
let vc = storyboard?.instantiateViewController(withIdentifier: storyboardId)
navigationController?.pushViewController(vc!, animated: true)
But its not working because this viewcontroller in not added to navigation stack.
class DetailCollectionViewCell: UICollectionViewCell, UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// let navigationController = UINavigationController()
// let storyboardId = "Login"
// let vc = storyboard?.instantiateViewController(withIdentifier: storyboardId)
// navigationController?.pushViewController(vc!, animated: true)
}
}
How do i solve this problem.
Any help is appreciated.
You have following options
1) Implement tableview datasource and delgate in viewController instead of collection view cell
2) Use Delegate (explained below )
3) Use Closures
4) Use NotificationCenter
You need to create delegate or protocol as collection view cell can't push or present view controller.
Here is simple example (This is not exact code you may need modification)
Create protocol
protocol TableViewInsideCollectionViewDelegate:class {
func cellTaped(data:IndexPath)
}
Inside your collectionview cell add weak property
weak var delegate:TableViewInsideCollectionViewDelegate?
Now in your ViewController class you in cellForItem method of collectionview
you need to set delegate to self
like
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCell", for: indexPath) as! CustomCollectionCell
cell.delegate = self
return cell
and implement delegate method in viewController class and write code to push your view controller from there like self.navigationController.push
Now In Goto Collectionview Cell method
and whenever your tableviewDidSelect called
call delegate method like self.delegate?.cellTaped(data: dataYouWantToPass)
Hope it is helpful
You have to check some info:
First:
Check your navigationController is nil or not
Second:
Check your initial view controller method is correct or not,
this is my way:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "StoryboardIdentifier") as? ViewController
You can solve this problem by using a delegation pattern use the following steps :
Confirm table view delegate to the collection view and collection view delegate to the respective view controller.
delegation chaining can be used to solve this problem. In this example, I have shown how you can pass data from table view cell to collection view cell.
Implement collection view delegate and data source methods.
Implement table view delegate and data source methods.
whenever did select row will get called the call delegate method to tell view controller that some row is selected and according to the row index change handle your navigation.
code example:
Step 1: Create a protocol.
protocol RowSelected : class {
func rowSelected(_ index : Int)
}
Step 2: Declare delegate variable in TableViewCell.
weak var delegate: RowSelected?
Step 3: In collection view confirm delegate and Implement delegated method.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellIdentifier", for: indexPath) as! CollectionViewCell
cell.delegate = self
return cell
extension CollectionViewCell : RowSelected {
func rowSelected() {
// Pass the information to view controller via a delegate/closure/notification. just like we passed information from table view cell to collection view cell and handle navigation accordingly.
}
}
Step 4: In ViewController you can confirm a delegate for collection view and implement it's delegate method and can handle navigation.
You can use a closure and notification center as well to inform view controller to navigate to next screen.
Related
I have a UITableView in a HomeViewController. This UITableView has one customized UITableViewCell in it.
I want to use the same UITableViewCell in ListViewController by dequeueing it. Below code does not work.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeVC: HomeViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
if let homeTableView = homeVC.tableView {
// Above homeVC.tableView is becoming nil.
// Would like to dequeue here.
}
Since I cannot see your HomeViewController code, I am assuming the tableView property is linked from the Interface Builder as #IBOutlet weak var tableView: UITableView!. This means that that tableView is not instantiated until the view actually appears on the screen. To prove this, try placing a breakpoint inside your HomeViewConntrollers viewDidLoad() method, and notice that it is never reached.
To solve your problem, I would add a table view into your ListViewController, and copy the custom table cell from HomeViewController into your table view. Then create a custom UITableViewCell, and in IB assign both cells in HomeViewController and ListViewController as your custom table view cell.
You can create a xib file and a custom UITableViewCell class for the cell you want to reuse in multiple view controllers. Then, in the xib's identity inspector, define the custom type of the cell as your custom class. Then, register the cell class in each view controller by calling register function on the tableView instance in viewDidLoad. Then dequeue your cell by implementing UITableViewDataSource's tableView(_:cellForRowAt:).
I have a tab bar controller application and in one of the tabs a UI Collection View Controller with an action assigned to a button. This button does its magic and then should change the tab bar view to another one. However, I'm not able to reference it right to the tab controller.
tabBarController is the class name assigned to the controller. So, I tried:
tabBarController.selectedIndex = 3
and also creating a method directly in the tabBarController class
tabBarController.goToIndex(3)
The error says: Instance member of 'goToIndex' cannot be used on type tabBarController
Any ideia?
Thank you,
Im having a little trouble understanding what you mean by referencing it right, but hopefully this will help. Assuming tabBarController is a subclass of UITabBarController:
class MyTabBarController: UITabBarController {
/// ...
func goToIndex(index: Int) {
}
}
In one of your tab controllers (UIViewController) you can reference your UITabBarController with self.tabBarController. Note that self.tabBarController is optional.
self.tabBarController?.selectedIndex = 3
If your tab UIViewController is a UIViewController inside a UINavigationController, then you will need to reference your tab bar like this:
self.navigationController?.tabBarController
To call a function on your subclass you would need to cast the tab bar controller to your custom subclass.
if let myTabBarController = self.tabBarController as? MyTabBarController {
myTabBarController.goToIndex(3)
}
Update based on comments:
You're correct that you cant access the tabBarController inside the cell unless you made it a property on either the cell itself (not recommended) or the app delegate. Alternatively, you could use target action on your UIViewController to call a function on the view controller every time a button is tapped inside a cell.
class CustomCell: UITableViewCell {
#IBOutlet weak var myButton: UIButton!
}
class MyTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReuseIdentifier", for: indexPath) as! CustomCell
/// Add the indexpath or other data as a tag that we
/// might need later on.
cell.myButton.tag = indexPath.row
/// Add A Target so that we can call `changeIndex(sender:)` every time a user tapps on the
/// button inside a cell.
cell.myButton.addTarget(self,
action: #selector(MyTableViewController.changeIndex(sender:)),
for: .touchUpInside)
return cell
}
/// This will be called every time `myButton` is tapped on any tableViewCell. If you need
/// to know which cell was tapped, it was passed in via the tag property.
///
/// - Parameter sender: UIButton on a UITableViewCell subclass.
func changeIndex(sender: UIButton) {
/// now tag is the indexpath row if you need it.
let tag = sender.tag
self.tabBarController?.selectedIndex = 3
}
}
I have a viewController where I have a tableview and inside every cell I have a collectionView.
I want to go to another view (where I will have a back button) so I tried to use my code which is working for the other views:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "detail") as! DetailView
vc.name = beachesNames[indexPath.row]
vc.imagak = imagesNames[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
But because this code is inside the TableViewCell I have an error that says:
Value of type 'HomeViewCell' has no member 'storyboard'
Is there any alternative parameter that I can use or something else it could do my task?
Thanks in advance.
- swift 3
let detailOrderVC = UIViewController()
self.viewController()?.navigationController?.pushViewController(detailOrderVC, animated: true)
- swift 4
in swift 4, you need to pass the viewcontroller to the cell like below
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! CustomTableViewCell
// some your logic
cell.viewController = self // CustomTableViewCell needs to have viewController member variable
return cell
}
let detailOrderVC = UIViewController()
self.viewController?.navigationController?.pushViewController(detailOrderVC, animated: true)
If you are pushing to a view class that requires layout you must call layout at the same time.
ex.
let layout = UICollectionViewFlowLayout()
let nextViewclass = NextClass(collectionViewLayout: layout)
self.navigationController?.pushViewController(nextViewclass, animated: true)
If you have a collection view inside of every cell, then I would recommend having each cell hold a container view. The view controller associated with the container view will have a storyboard.
The code for the push would look a bit ugly:
self.parent?.navigationController?.pushViewController(vc, animated: true)
The parent?. goes from the contained view controller to the parent view controller.
What you're missing is instantiating the storyboard:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
storyboard.instantiateViewControllerWithIdentifier("myNavigationController") as! UINavigationController
But as this stackoverflow answer says, this generally isn't a good MVC design.
You might want to look into delegate patterns or using NotificationCenter. Both these ways essentially trigger a call to the parent view which will then handle the corresponding action.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let storyboard = UIStoryboard(name: "FilteringPageSelection", bundle: nil)
controller = storyboard.instantiateViewControllerWithIdentifier("FilteringPageSelectionController") as! FilteringPageSelectionTableViewController
controller.filteringType = filterTitle[indexPath.row];
controller.selectedValue = selectedValue;
controller.title = "Selection"
self.navigationController?.pushViewController(controller, animated: true)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
Above is my code, this is my table view controller code, i am intend to push to another view controller by clicking it cell. Inside the new controller, there is multiple value for me to choose. however , after i select the new value from the new controller, i back to the original table view scene and get the selected value from the new controller and assign into the variable selectedValue. Below is my code of the unwind segue.
#IBAction func unwindToFilteringVC(segue:UIStoryboardSegue) {
selectedValue = controller.selectedValue
}
However, when i click the cell again, since the tableview override function only initialize for the first time when the tableview is loaded, so when i change the value of the selectedValue, the controller.selectedValue still remain the old value and push into new controller.
How do i changes the value of controller.selectedValue?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) - this function gets called every time you tap on a cell, not just once. So whenever you select a cell, your code instantiates the filter view controller and sets its selectedValue to the current selectedValue of the table view controller (unless that is a global variable). Maybe that is not what you want.
OR
You may have mixed which selectedValue is which. You should rename one.
I think what you need to use is a delegate pattern to inform the table view controller of the selected value in the selection view controller instead of holding a reference to it. First you define a protocol:
protocol SelectionViewControllerDelegate: class {
func selectionViewController(controller: SelectionViewController, didSelectValue value: ValueType)
}
You add a weak reference to an optional property that conforms to that protocol and you inform the delegate of changes in the selected value right where you handle the selection action in your selection view controller.
class SelectionViewController: UIViewController {
weak var delegate: SelectionViewControllerDelegate?
func handleChange() {
delegate?.selectionViewController(self, didSelectValue: selectedValue)
}
}
Then you make your table view controller conform to this protocol like so:
class TableViewController: UITableViewController, SelectionViewControllerDelegate {
func selectionViewController(controller: SelectionViewController, didSelectValue value: ValueType) {
self.selectedValue = value
}
}
Don't forget to set the table view controller as the delegate of the selection view controller in the didSelectRowAtIndexPath method by saying:
controller.delegate = self
Right before you call navigationController?.pushViewController method.
How can I add a view of a child view controller to a custom UITableViewCell? I can add the view like this inside cellForRowAtIndexPath:
self.addChildViewController(controlsViewController)
cell!.cellView.addSubview(controlsViewController.view)
controlsViewController.didMoveToParentViewController(self)
But when the cell disappears, I need to remove this child view controller. I'm not really sure how to do that. Is there a better way to go about this?
Do it via delegation. I have done on collection view ,you can do it on tableview too. follow the below steps
1 .In your custom cell class create a delegateHandler and override your awakeFromNib() method. eg
protocol BibleReadingSliderProtocol: class {
func addThisViewControllerAsChild(audioViewController :AudioViewController)
}
class BibleReadingSliderCollectionCell: UICollectionViewCell {
#IBOutlet weak var containerView: UIView!
var audioVC = AudioViewController()
weak var bibleReadingSliderDelegate:BibleReadingSliderProtocol?
override func awakeFromNib() {
super.awakeFromNib()
print("Awake call from cell")
// Initialization code
let storyboard = UIStoryboard(name: "Main", bundle: nil)
audioVC = storyboard.instantiateViewController(withIdentifier: "AudioViewController") as! AudioViewController
audioVC.view.frame = self.containerView.bounds
self.containerView.addSubview(audioVC.view)
if self.bibleReadingSliderDelegate != nil {
self.bibleReadingSliderDelegate?.addThisViewControllerAsChild(audioViewController: audioVC)
}
}
}
In your ViewController where you are using this custome cell (either tableview or collection view) define the delegate hander
func addThisViewControllerAsChild(audioViewController: AudioViewController) {
self.addChildViewController(audioViewController);
}
And Dont forget to set your delegate to this viewcontroller in cellForItemAt/cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let imageSliderCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! BibleReadingSliderCollectionCell
imageSliderCollectionViewCell.bibleReadingSliderDelegate = self
return imageSliderCollectionViewCell
}
Don't misunderstand MVC. Not every view in the world needs to have its own personal view controller! A main view has a view controller, but a button in that main view does not have its own personal view controller; it simply talks to the main view's view controller.
The same is true of this view. Views can come and go very easily; do not add the heavyweight burden of an additional view controller when you don't need to! Just grab the view (somehow) and stick it into the cell's contentView or remove it from the cell's contentView in cellForRowAtIndexPath:, just like any other view - but manage it using your table view controller or table view data source / delegate or whatever is in charge here. Don't add an extra view controller to the story just for the sake of this one little view. That's likely to be a bad use of view controllers.