Please assist. What I'm trying to achieve
is that when I tap on a specific collectionViewCell the ReportDetailsMapController is pushed and my reports[indexPath.item] is sent from MainController to the ReportDetailsMapController.
PROTOCOL:
protocol MainControllerDelegate: class {
func sendReport(data: ReportModel)
}
FIRST VC:
class MainController: UIViewController, UICollectionViewDelegate,UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var delegate: MainControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let report = reports[indexPath.item]
//When I print(report.name) here. Everything executes correctly
self.delegate?.sendReport(data: report)
self.navigationController?.pushViewController(ReportDetailsMapController(), animated: true)
}
}
SecondVC:
class ReportDetailsMapController: UIViewController, MainControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mc = MainController()
mc.delegate = self
}
func sendReport(data: ReportModel) {
print(data.name)//This does not execute when ReportDetailsMapController is loaded.
print("Report sent")
}
}
ReportModel:
class ReportModel: NSObject {
var name: String?
var surname: String?
var cellNumber: String?
}
That method is not called because you didn't assign the view controller you're pushing to the delegate property.
When the cell is selected, you could do in this order: initialize the view controller and assign it to the delegate, then call the delegate method and push that view controller:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let report = reports[indexPath.item]
//When I print(report.name) here. Everything executes correctly
let viewController = ReportDetailsMapController()
self.delegate = viewController
self.delegate?.sendReport(data: report)
self.navigationController?.pushViewController(viewController, animated: true)
}
However, I think a simpler and more elegant way would be to simply create that property on the ReportDetailsMapController and inject it before pushing it.
There's a similar question/answer related to that here: passing data from tableview to viewContoller in swift
Related
Can't access to function in parent view controller from a child via delegate/protocol. print() doesn't print.
In Child VC I have this:
protocol MyViewControllerDelegate: class {
func requestExpandedView()
}
class MyViewController: UIViewController {
weak var delegate: MyViewControllerDelegate?
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("\(indexPath) didSelectItemAt")
delegate?.requestExpandedView()
}
}
In Parent VC I have this:
extension MessagesViewController: MyViewControllerDelegate {
func requestExpandedView() {
print("Done") // doesn't print anything
requestPresentationStyle(.expanded)
}
}
What's wrong?
You need to set the delegate of MyViewController when creating its instance, i.e.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "MyViewController") as! MyViewController
vc.delegate = self
From First ViewController I press a Button and open a SecondViewController that view a collection of Image, when I tap on image the view show a full screen image and return at the collection View. Can I pass the image to the first (starting) viewcontroller to use it as a background?
class SecondViewController: UIViewController {
#IBAction func returnHome(_ sender: UIButton) {
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "backToHome", sender: nil)
}
var images = ["bg13", "bg11", "bg6", "bg10", "bg12", "bg5", "bg3", "bg4", "bg2", "bg7", "bg8", "bg9", "bg1", "bg14"]
// ...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let imageViewCollection = UIImageView(image:UIImage(named: images [indexPath.item]))
imageViewCollection.frame = self.view.frame
imageViewCollection.backgroundColor = .black
imageViewCollection.contentMode = .top
imageViewCollection.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
imageViewCollection.addGestureRecognizer(tap)
imageViewCollection.addGestureRecognizer(tap)
imageViewCollection.isUserInteractionEnabled = true
self.view.addSubview(imageViewCollection)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
}
You can use Delegate to pass the image to the first view controller.
Write this protocol on the top of the Second View Controller.
protocol SecondViewControllerDelegate: NSObject {
func sendToFirstViewController(image: UIImage)
}
Then create a variable inside Second View Controller
class SecondViewController: UIViewController {
....
weak var customDelegate: SecondViewControllerDelegate?
....
}
Call this from collectionView:didSelectItemAt:indexPath method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let image = UIImage(named: images [indexPath.item])
customDelegate?.sendToFirstViewController(image: image)
...
}
In the First viewController, in where you instantiate the second view controller, put this line
secondVc.customDelegate = self
then create an extension to implement the delegate method
extension FirstViewController: SecondViewControllerDelegate {
func sendToFirstViewController(image: UIImage){
// use this image as your background
}
}
Stackoverflow
I know how to make a button in the table view cells with website links, rate, mail, and many things. However, How could I open the view controller with the instantiateViewController in the #Objc func's statements?
For example.
Create a new Table View Cell folder called FeedBackButtonsTableViewCell
class FeedBackButtonsTableViewCell: UITableViewCell {
#IBOutlet weak var ButtonCells: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Let create a new view controller folder called
class FeedbackViewController: UIViewController {
#IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Feedback"
}
}
add the extension to calling the view controller to UITableViewDataSource and UITableViewDelegate and create a obj func statements inside of the second FeedbackViewController with UITableViewDataSource and UITableViewDelegate under the cells.
extension FeedbackViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
if indexPath.row == 1 {
buttonCell = TableView.dequeueReusableCell(withIdentifier: "ButtonCells") as? FeedBackButtonsTableViewCell
buttonCell?.ButtonCells.addTarget(self,action: #selector(LearnMore),for: .touchUpInside)
buttonCell?.ButtonCells.tag = indexPath.row
return buttonCell!
}
#objc func LearnMore() {
// How could I write to open the view controller with UIButton in the Table View Cells?
}
}
Thank you for bring a kind of help! :)
Simple solution could be to use procol.
protocol CellActionDelegate{
func didButtonTapped(index: Int)
}
Now confirm the protocol in FeedbackViewController. Take index and actionDelegate properties in your UITableViewCell subclass.
class FeedBackButtonsTableViewCell: UITableViewCell{
var actionDelegate: CellActionDelegate?
var index: Int?
.....
// Take Action of UIButton here
#IBAction func more(_ sender: Any) {
if let delegate = self.actionDelegate{
delegate.didButtonTapped(index!)
}
}
}
Now in your FeedbackViewController set actionDelegate & Corresponding index in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
you can open anotherView controller from func didButtonTapped(index: Int) definition .
extension FeedbackViewController:CellActionDelegate{
func didButtonTapped(index: Int) {
let storybord = UIStoryboard(name: "Main", bundle: nil)
guard let controller = storybord.instantiateViewController(withIdentifier: "AnotherControllerIdentfier") as? AnotherViewController else{
fatalError("Could not finc another view controller")
}
self.present(controller, animated: true, completion: nil)
}
}
#objc func LearnMore() {
let viewController = FeedbackDetailsViewController()// creation of viewController object differs depends on how you fetch the UI, means either you are using storyboard or xib or directly making ui in code.
self.navigationController?.pushViewController(viewController, animated: true)
}
Im trying to click on did select item then present a ui collection view controller with the user a the index path of who i just clicked on though nothing loads on screen?
class HomePage: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var profilePage: ProfilePage?
var profilePageHeader: ProfilePageHeader?
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let user = users[indexPath.item]
let PP = ProfilePage(collectionViewLayout: UICollectionViewFlowLayout())
self.profilePageHeader?.currentUser = user
self.profilePage?.user = user
navigationController?.pushViewController(PP, animated: true)
}
}
class ProfilePage: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var user: User2?{
didSet{
let displayName = user?.DisplayName
navigationItem.title = displayName
collectionView?.reloadData()
}
}
}
the problem occur because collectionView was not loaded at the time where you call collectionView?.reloadData()
the usual thing I did was to call binding function on setting the data, and on viewDidLoad like
class ProfilePage: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var user: User2?{
didSet{
bind()
}
}
func bind(){
navigationItem.title = user?.DisplayName
collectionView?.reloadData()
}
override viewDidLoad(...) {
...
bind()
}
}
that way, your binding will be called whenever data is ready, and when view was ready. the first call is sure a fail, because either data or view was not ready at the moment, but the second call will be success because both data and view will be ready at that moment,
I have a function within a UICollectionViewCell that requires access to the
hosting UIViewController. Currently 'makeContribution()' can't be accessed:
What is the proper way of accessing the host UIViewController that has the desired function?
Thanks to the insightful responses, here's the solution via delegation:
...
...
...
{makeContribution}
This is a mildly controversial question - the answer depends a little on your philosophy about MVC. Three (of possibly many) options would be:
Move the #IBAction to the view controller. Problem solved, but it might not be possible in your case.
Create a delegate. This would allow the coupling to be loose - you could create a ContributionDelegate protocol with the makeContribution() method, make your view controller conform to it, and then assign the view controller as a weak var contributionDelegate: ContributionDelegate? in your cell class. Then you just call:
contributionDelegate?.makeContribution()
Run up the NSResponder chain. This answer has a Swift extension on UIView that finds the first parent view controller, so you could use that:
extension UIView {
func parentViewController() -> UIViewController? {
var parentResponder: UIResponder? = self
while true {
if parentResponder == nil {
return nil
}
parentResponder = parentResponder!.nextResponder()
if parentResponder is UIViewController {
return (parentResponder as UIViewController)
}
}
}
}
// in your code:
if let parentVC = parentViewController() as? MyViewController {
parentVC.makeContribution()
}
Well, CollectionView or TableView?
Anyway, Set your ViewController as a delegate of the cell. like this:
#objc protocol ContributeCellDelegate {
func contributeCellWantsMakeContribution(cell:ContributeCell)
}
class ContributeCell: UICollectionViewCell {
// ...
weak var delegate:ContributeCellDelegate?
#IBAction func contributeAction(sender:UISegmentedControl) {
let isContribute = (sender.selectedSegmentIndex == 1)
if isContribute {
self.delegate?.contributeCellWantsMakeContribution(self)
}
else {
}
}
}
class ViewController: UIViewController, UICollectionViewDataSource, ContributeCellDelegate {
// ...
func collectionView(_ collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
cell = ...
if cell = cell as? ContributeTableViewCell {
cell.delegate = self
}
return cell
}
// MARK: ContributeCellDelegate methods
func contributeCellWantsMakeContribution(cell:ContributeCell) {
// do your work.
}
}