iOS Swift Custom Cell NavigationController - ios

I have CollectionView in TableView. everything ok but. when I want to navigate my cell to another viewController I got error
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let bookView: BookSingleController = storyboard.instantiateViewControllerWithIdentifier("BookSingle") as! BookSingleController
self.navigationController?.pushViewController(bookView, animated: true)
bookView.bookID = "\(self.books[indexPath.row].id)"
print(self.books[indexPath.row].id)
}
Xcode show me error on self.navigationController?.pushViewController(bookView, animated: true) line. this is error description:
Value of 'RelatedBookTableViewCell' has no member 'navigationController'
RelatedBookTableViewCell is my custom cell class:
class RelatedBookTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
Where is my problem?
thanks.

You will either need to:
Use a delegate pattern like some have suggested.
Where you pass the call from the collectionView delegate method didSelectItemAtIndexPath to your own custom delegate/protocol on the cell that the UITableViewController which displays the cell is a delegate to. This can be set in the cellForRowAtIndexPath.
swift
Custom Protocol
protocol DisplayBookDelegate {
func displayBook(bookId: String)
}
In the tableViewController
class tableViewController: UITableViewController, DisplayBookDelegate {
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.delegate = self
return UITableViewCell()
}
func displayBook(let bookId) {
self.navigationController?.pushViewController(bookView, animated: true)
}
}
In the cell
var delegate: DisplayBookDelegate?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.delegate.displayBook("1")
}
Use notifications and pass the bookId in a dictionary on the notification object
objc: [[NSNotificationCenter defaultCenter] postNotificationName:"kDisplayBookView" object:#{"bookId":"1"}];
swift NSNotificationCenter.defaultCenter().postNotificationName("kDisplayBookView", object: ["bookId":"1"])

Using below code you can get the Top viewcontroller and using that view controller you can perform your push operation
let objViewController = UIApplication.topViewController()!
Use this extension
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}

Try pushing the VC from the root controller in your application.
UIApplication.sharedApplication().keyWindow?.rootViewController

Related

Opening another view controller using delegate?

I'm new to IOS, creating a custom calendar where if the user selects any date, the app will redirect to another viewController.
I used this link to create a calendar:
https://github.com/Akhilendra/calenderAppiOS
I have done it with delegate but I can't figure out what I did wrong.
My code:
protocol SelectedDateDelagate: class {
func openAppointmentDetails()
}
class CalenderView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, MonthViewDelegate {
weak var delegate: SelectedDateDelagate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell=collectionView.cellForItem(at: indexPath)
cell?.backgroundColor=Colors.darkRed
let lbl = cell?.subviews[1] as! UILabel
lbl.textColor=UIColor.yellow
let calcDate = indexPath.row-firstWeekDayOfMonth+2
print("u selected date",calcDate,monthName)
delegate?.openAppointmentDetails()
}
}
class ViewController: UIViewController,SelectedDateDelagate {
func openAppointmentDetails() {
let myVC = storyboard?.instantiateViewController(withIdentifier: "appointmentDetailsVC") as! AppointmentDetailsViewController
navigationController?.pushViewController(myVC, animated: true)
}
}
now the problem is when I clicked on date nothing gonna happen.
It's better to use the storyboard in this case, and control-drag your collection view to the second view controller. Since you don't use your delegate for anything else other than presenting the view controller, you might as well just get rid of it.
Delegates are used in other cases. In this situation, it is not necessary. In addition, you do not need to conform UIView to Delegates and DataSource because it contradicts MVC. Instead, put the calendar view (collection view) inside the UIViewController and conform this controller to implement methods.
You should do this:
class CalenderViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var calenderView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)!
cell.backgroundColor = Colors.darkRed
let lbl = cell.subviews[1] as! UILabel
lbl.textColor = UIColor.yellow
let calcDate = indexPath.row - firstWeekDayOfMonth + 2
openAppointmentDetails()
}
func openAppointmentDetails() {
let appointmentVC = storyboard?.instantiateViewController(withIdentifier: "appointmentDetailsVC") as! AppointmentDetailsViewController
navigationController?.pushViewController(appointmentVC, animated: true)
}
}
I solved this. I forgot to add calenderView.delegate = self
in viewDidLoad() method of ViewController
and also changes in openAppointmentDetails()
here are the changes.
class ViewController: UIViewController,SelectedDateDelagate {
func openAppointmentDetails() {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "appointmentDetailsVC") as! AppointmentDetailsViewController
self.present(nextViewController, animated:true, completion:nil)
}
override func viewDidLoad() {
calenderView.delegate = self
}
}

iOS Swift: Pushing a View Onto the Stack From Within a Custom Tableview Cell

I have a tableview inside a VC that has a navigation controller and it contains custom table cells. I was wondering what the best practice is for pushing onto the parent VC's navigation stack if a button in the custom table cell is tapped. I am able to get this to work if i pass the parent VC's navigation controller to the cell; but is this the most effective/efficient practice? Please see my current implementation below:
UserAccountVC:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TextPostTableViewCell = Bundle.main.loadNibNamed("TextPostTableViewCell", owner: self, options: nil)?.first as! TextPostTableViewCell
cell.setupCell(navigationController: self.navigationController!)
cell.selectionStyle = .none
return cell
}
CustomTableCell:
import UIKit
class TextPostTableViewCell: UITableViewCell {
var aNavigationController: UINavigationController!
//MARK: Actions
#IBAction func profilePicButtonTapped() { //We want to present a users profile
let sb = UIStoryboard(name: "SuccessfulLogin", bundle: nil)
let cc = (sb.instantiateViewController(withIdentifier: "otherUserViewController")) as! OtherUserAccountViewController
self.aNavigationController.pushViewController(cc, animated: true)
}
func setupCell(navigationController: UINavigationController) -> Void {
aNavigationController = navigationController
}
}
Thank you in advance!
No, this is not best practice. You can setup an IBAction in interface builder for your UIButton or add your UIViewController as a target in cellForRowAt. With either method you may need some method of identifying the indexPath, since you are not using didSelectRow in your tableview delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TextPostTableViewCell = Bundle.main.loadNibNamed("TextPostTableViewCell", owner: self, options: nil)?.first as! TextPostTableViewCell
cell.button.tag = indexPath.row // Or use some other method of identifying your data in `myAction(_:)`
cell.button.addTarget(self, action:, #selector(myAction(_:)), for: .touchUpInside)
...
}
You can use delegate in this situation.
The code is a bit more here, but this is better way in iOS development IMO.
class ViewController: UIViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TextPostTableViewCell = Bundle.main.loadNibNamed("TextPostTableViewCell", owner: self, options: nil)?.first as! TextPostTableViewCell
cell.delegate = self
cell.selectionStyle = .none
return cell
}
}
extension ViewController: TextPostTableViewCellDelegate {
func didTappedProfilePicButton() {
let sb = UIStoryboard(name: "SuccessfulLogin", bundle: nil)
let cc = (sb.instantiateViewController(withIdentifier: "otherUserViewController")) as! OtherUserAccountViewController
navigationController?.pushViewController(cc, animated: true)
}
}
protocol TextPostTableViewCellDelegate: class {
func didTappedProfilePicButton()
}
class TextPostTableViewCell: UITableViewCell {
weak var delegate: TextPostTableViewCellDelegate?
//MARK: Actions
#IBAction func profilePicButtonTapped() { //We want to present a users profile
delegate?.didTappedProfilePicButton()
}
}

Pass info between collection views that have been embedded in different view controllers

So I have 2 view controllers, the one shown on the left has 1 embedded collectionview in it and the one shown on the right has 2 embedded collectionviews in it.
when trying to pass data (packSelected) from one to another, the prepareforsegue method doesn't work as i want it to change view controllers and the information is coming from the UICollectionViewController class. so at the moment I'm using the following code to 'segue' between the 2 view controllers from the UICollectionViewController class when a cell of the collectionview which is embedded in the the left hand viewcontroller is selected:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PackCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PackCollectionViewCell
//cell.labelCell.text = tableData[indexPath.row]
//cell.imageCell.image = UIImage(named: tableImages[indexPath.row])
cell.labelCell.text = packsDescription[indexPath.row]
cell.imageCell.image = UIImage(named: packsImage[indexPath.row])
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height/2
cell.imageCell.layer.borderWidth = 3
cell.imageCell.layer.borderColor = UIColorFromHEX(hexValue: 0x62aca2).cgColor
return cell
}
var packSelected: String = ""
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(packsName[indexPath.row])
packSelected = packsName[indexPath.row]
//performSegue(withIdentifier: "packView2Journey", sender: self)
transportMe()
}
func transportMe() {
let storyboard: UIStoryboard = UIStoryboard (name: "Main", bundle: nil)
let vc: JourneyViewController = storyboard.instantiateViewController(withIdentifier: "JourneyViewController") as! JourneyViewController
let currentController = getCurrentViewController()
currentController?.present(vc, animated: false, completion: nil)
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
The problem with this is now that I can't send any data to the other collectionview (s) that are embedded in the right hand viewcontroller.
--------------- EDIT ---------------------------------
solved with delegate method.
in the collectionviewcontroller that will pass the information declare the delegate:
class PackCollectionViewController: UICollectionViewController {
weak var delegate: PackCollectionViewDelegate?
//....
}
then below create delegate class:
protocol PackCollectionViewDelegate : class {
func packSelected (_ selector: PackCollectionViewController, didSelect thePackName: String)
}
and in the function that calls transportMe() as shown in original post define the data to be passed:
let passPackName = packsName[indexPath.row] as String
self.delegate?.packSelected(self, didSelect: passPackName)
transportMe()
in the collectionviewcontroller that will receive the information create extension:
extension PartCollectionViewController : PackCollectionViewDelegate {
func packSelected (_ selector: PackCollectionViewController, didSelect thePackName: String) {
print(thePackName)
}
}

tvOS performsegue in a nested TableViewController

I have been having issues calling the performsegue method on my custom CollectionViewCell. My view hierarchy is UIView-UITableView-UICollectionView. The tableview is a static tableview with a collectionview inside my "CustomTableViewCell". Because I am using tvOS I read I should be using a UITapGestureRecognizer instead of the collectionview(didSelectCell) function. Here is my method "tapped" which I know is hooked up properly because my print function works, the issue is I am getting an error "Value of type CustomTableViewCell has no member "perfromSegue" when I add the "self.performsegue" line. I tried to control+drag a segue from the cell to my next view but still nothing. I assume it has to do with the the type of class my "CustomTableViewCell" is but I am not sure what else to add to it.
cell for item which adds the gesture:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCollectionViewCell
if cell.gestureRecognizers?.count == nil {
let tap = UITapGestureRecognizer(target: self, action: #selector(CustomTableViewCell.tapped(_:)))
tap.allowedPressTypes = [NSNumber(value: UIPressType.select.rawValue)]
cell.addGestureRecognizer(tap)
}
here is the tapped method:
func tapped(_ gesture: UITapGestureRecognizer){
if let cell = gesture.view as? PostCollectionViewCell {
//load next view pass movie
guard let post = cell.post else {return;}
print("\(post.title) tapped")
self.performSegue(withIdentifier: "toPost", sender: post)
}
}
I've tried to replace the self.performsegue with cell.perfomsegue because technically that is where I get the information to pass in my "perfromSegue(identifier:) method. The last thing I was thinking is to somehow call the parent view of the CustomTableViewCell which would be the original ViewController but I cannot call the .performsegue on ViewController or ViewController.sharedController when I create a sharedController
Add an extension to your uitableviewcell
extension UITableViewCell {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.nextResponder()
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
then in didSelectItemAtIndexPath
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
collectionView.deselectItemAtIndexPath(indexPath, animated: true)
if let viewController = parentViewController as? NameOfYourViewController {
viewController.performSegueWithIdentifier("seguename", sender: nil)
}
}

Is it possible to add a tick mark(image) to my TableView cell when a button is tapped in other screen after tableView didselect method?

I have a tableview which Contains 10 cell named one to ten respectively. When I select a cell a new screen opens which contains a button. I want to know is it possible when I tap that button the tableView cell related to that button gets a tick mark image? if yes then how?
By "new screen" do you mean a new view controller? You can do it by the Delegate pattern.
protocol MainViewControllerDelegate {
func buttonOnClick(indexPath: NSIndexPath)
}
class MainViewController: UIViewController, MainViewControllerDelegate {
func buttonOnClick(indexPath: NSIndexPath) {
guard let cell = tableView.cellForRowAtIndexPath(indexPath) else {
return
}
cell.accessoryType = .Checkmark
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let newViewController = NewViewController()
newViewController.delegate = self
newViewController.indexPath = indexPath
self.presentViewController(newViewController, animated: false, completion: nil)
}
}
class NewViewController: UIViewController {
var delegate: MainViewControllerDelegate!
var indexPath: NSIndexPath!
func buttonOnClick() {
delegate.buttonOnClick(indexPath)
}
}

Resources