I have a viewController that inherits from UITabBarController .
I am trying to override the selectedIndex variable so that I can get notified when it gets changed with the didSet like the code below.
override var selectedIndex: Int {
didSet {
refreshTabBar()
}
}
The problem is that that function is not getting called when tabs get changed and I need to know why.
PS: I do not want to call it from the didSelect delegate method.
Thanks.
As apple documentation says about selectedIndex:
This property nominally represents an index into the array of the
viewControllers property.
So it's computed property which returns firstIndex of selectedViewController from viewControllers.
And on setting it changes the selectedViewController.
Use some other UITabBarController property instead. F.e:
override var selectedViewController: UIViewController? {
didSet {
print(selectedIndex)
refreshTabBar()
}
}
Set selectedIndex programmatically to call the function
class FirstViewController: TabViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.selectedIndex = 1
}
}
class TabViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
override var selectedIndex: Int{
didSet {
refreshTabBar()
}
}
}
Related
I'm trying to reload table view in Calculation controller, pressing back navigation button on Setup controller (red arrow on screenshot).
Which is the best way to do it?
Thanks !
In a navigation controller its's pretty easy. In Swift the most efficient way is a callback closure, it avoids the overhead of protocol/delegate.
In SetupController declare a callback property, a closure with no parameter and no return type
var callback : (() -> Void)?
and call it in viewWillDisappear. viewWillDisappear is allways called when the back button is pressed.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
callback?()
}
In CalculationController assign the callback in prepare(for
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
...
let setupController = segue.destination as! SetupController
setupController.callback = {
self.tableView.reloadData()
}
Using Delegate Pattern
Create delegate with some method for second ViewController. Implement this protocol to first ViewController and when this method is called, reload UITableView data (in overriden prepare(for:sender:) set delegate of second ViewController to self). When second ViewController will disappear, call method on delegate variable of second ViewController.
Now when you're able to use delegates, you can easily add parameter to delegate's method and pass data from second to first ViewController.
protocol SecondVCDelegate: class { // define delegate protocol
func controllerDismissed()
}
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.delegate = self
}
}
}
extension ViewController: SecondVCDelegate {
func controllerDismissed() { // this is called when you call delegate method from second VC
tableView.reloadData()
}
}
class SecondViewController: UIViewController {
weak var delegate: SecondVCDelegate? // delegate variable
override func viewWillDisappear(_ animated: Bool) {
delegate?.controllerDismissed() // call delegate's method when this VC will disappear
}
}
An easy solution is to reload the tableView, when the view is going to appear again.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
Alternative solutions could be to implement unwindSegue or delegation.
to achieve this you have multiple solutions first of all you have to know what is the best to use by your case,
1- are you passing data back to the CalculationVC
2- do you just need to reload the CalculationVC each time it appears ?
for the first case you use what called Delegates in swift.
for the second case you can use a life-cycle function that is called viewWillAppear() in the ViewController.
for the Delegate case you can find tons of articles online this one recommended for newbies !
and for the second case just use this code.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
Try this code
protocol VC2Delegate: class {
func viewController(_ myVC2: VC2?, didFinishEditingWithChanges hasChanges: Bool)
}
class VC2 {
private weak var: VC2Delegate? delegate?
weak var: VC2Delegate? delegate?
#IBAction func finishWithChanges() {
delegate.viewController(self, didFinishEditingWithChanges: true)
}
#IBAction func finishWithoutChanges() {
delegate.viewController(self, didFinishEditingWithChanges: false)
}
}
//VC1: implement the VC2Delegate protocol
class VC1: VC2Delegate {
var: Bool _needsReload?
func awakeFromNib() {
super.awakeFromNib()
needsReload = true
}
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadTableIfNeeded()
}
#IBAction func displayVC2() {
}
I have ViewController and there is UIView in it.
This UIView has separate class myView and there are many UI elements - one of them is CollectionView.
What I want is to perform segue when one of collection elements in myView is selected. But when I try to add line
performSegue(withIdentifier: "myIdintifier", sender: self)
to collection's view didSelectItemAt method I get error
Use of unresolved identifier 'performSegue'
And I understand that this is because I do it inside class that extends UIView and not UIViewController.
So how can I perfrom segue in this case? And also how can I prepare for segue?
Here I am going to evaluate it in step by step manner.
Step - 1
Create custom delegate using protocol as below snippet will guide you on your custom UIView. protocol must exist out of your custom view scope.
protocol CellTapped: class {
/// Method
func cellGotTapped(indexOfCell: Int)
}
Don't forgot to create delegate variable of above class as below on your custom view
var delegate: CellTapped!
Go with your collection view didSelect method as below
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if(delegate != nil) {
self.delegate.cellGotTapped(indexOfCell: indexPath.item)
}
}
Step - 2
Let's come to the your view controller. give the CellTapped to your viewcontroller.
class ViewController: UIViewController,CellTapped {
#IBOutlet weak var myView: MyUIView! //Here is your custom view outlet
override func viewDidLoad() {
super.viewDidLoad()
myView.delegate = self //Assign delegate to self
}
// Here you will get the event while you tapped the cell. inside it you can perform your performSegue method.
func cellGotTapped(indexOfCell: Int) {
print("Tapped cell is \(indexOfCell)")
}
}
Hope this will help you.
You can achieve using protocols/delegates.
// At your CustomView
protocol CustomViewProtocol {
// protocol definition goes here
func didClickBtn()
}
var delegate:CustomViewProtocol
#IBAction func buttonClick(sender: UIButton) {
delegate.didClickBtn()
}
//At your target Controller
public class YourViewController: UIViewController,CustomViewProtocol
let customView = CustomView()
customView.delegate = self
func didClickSubmit() {
// Perform your segue here
}
Other than defining protocol, you can also use Notification.
First, extent nonfiction.name:
extension Notification.Name {
static let yourNotificationName = Notification.Name(“yourNotificationName”)
}
Then right where you want to perform segue but can’t in your custom UIView:
NotificationCenter.default.post(name: .yourNotificationName, object: self)
Finally, you can listen to the notification in your viewControllers:
private var observer: NSObjectProtocol?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
observer = NotificationCenter.default.addObserver(forName: .yourNotificationName, object: nil, queue: nil) {notification in
self.performSegue(withIdentifier:”your segue”, sender: notification.object}
Don’t forget to remove it:
override func viewWillDisappear(_ animated: Bool){
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(observer)
}
I have this in my storyboard.
ViewController -> Tableview -> Custom cell
the custom cell has inside a collection view.
Inside my ViewController i have some filter buttons that i want to filter the array data of the collection view and reload it also.
So I tried to make a delegate method with the following code
protocol ReloadTheDataDelegate: class {
func reloadTheCV()
}
class NearMeViewController: UIViewController {
weak var delegate: ReloadTheDataDelegate?
#IBAction func anAction(_sender : AnyObject){
requests.weekendEventData.sort() { $0.realDate < $1.realDate }
delegate?.reloadTheCV()
}
}
class WeekendTableViewCell: UITableViewCell, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, ReloadTheDataDelegate {
#IBOutlet public weak var weekendCV: UICollectionView!
func reloadTheCV() {
print("done") //never printed
weekendCV.reloadData()
}
override func awakeFromNib() {
super.awakeFromNib()
if let myViewController = parentViewController as? NearMeViewController {
myViewController.delegate = self
}
weekendCV.register(UINib(nibName: "WeekendCollectionViewCell", bundle: nil),
forCellWithReuseIdentifier: "phoneweekendcell")
weekendCV.delegate = self
weekendCV.dataSource = self
}
}
And the extension that i take the ViewController
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
Could anyone explain why the function reloadTheCV() is never called ?
NearMeViewController delegate (ReloadTheDataDelegate ) is nil. Due to that its not printing. You didn't set the delegate and this approach is too complex also.
So, Instead of approaching with protocol concept. Simply you can reload the cell's collection view with the following manner.
Add the following function on your NearMeViewController. And call whenever you need reload the collection view.
func reloadCellCV() {
for cell in tableView.visibleCells {
if let cell = cell as? WeekendTableViewCell {
DispatchQueue.main.async {
cell.weekendCV.reloadData()
}
}
}
}
The following code should show two ways to pass information from an embedded controller (UICollectionView) back to a detailed view controller using either the Responder Chain OR delegate approach. Both approaches use the same protocol, and delegate method. The only difference is if I comment out the delegate?.method line in didSelectItemAtIndex path, the Responder Chain works. BUT, if I comment out the Responder Chain line in the didSelectItemAtIndex method, the uncommentented delegate? property doesn't call the method, and remains nil.
Protocol defined and included above DetailViewController. Needed for both approaches.
protocol FeatureImageController: class {
func featureImageSelected(indexPath: NSIndexPath)
}
Delegate property declared in the custom UICollectionViewController class, which is only needed for delegate approach.
class PhotoCollectionVC: UICollectionViewController
{
weak var delegate: FeatureImageController?
In DetailViewController, an instance of PhotoCollectionVC() is created, and the delegate property set to self with the delegate protocol as type.
class DetailViewController: UIViewController, FeatureImageController
{...
override func viewDidLoad() {
super.viewDidLoad()
let photoCollectionVC = PhotoCollectionVC()
photoCollectionVC.delegate = self as FeatureImageController
Within the collection view controller's didSelectItemAtIndexPath method, pass back the selected indexPath via either the Responder Chain (commented out) OR the delegate to the featureImageSelected method in the DetailVC.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
// if let imageSelector = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
// imageSelector.featureImageSelected(indexPath)
// }
self.delegate?.featureImageSelected(indexPath)
}
An instance of elegate method in DetailViewController. Needed for both.
func featureImageSelected(indexPath: NSIndexPath) {
record?.featureImage = record?.images[indexPath.row]
self.configureView()
}
Why would the Responder Chain approach work, but the delegate not?
There are no compiler or run time errors. Within the didSelectItemAtIndexPath method, the delegate always returns nil and nothing prints from the delegate method.
Your responder code calls a featureImageSelected on self:
self.featureImageSelected(indexPath)
but the delegate code calls featureImageSelected on the delegate:
self.delegate.featureImageSelected(indexPath)
Which would be the DetailVC's delegate, not the collectionViews delegate. Im not really sure what your code is doing, but you probably want something like
collectionView.delegate?.featureImageSelected(IndexPath)
which looks like it would just end up being
self.featureImageSelected(indexPath)
The error in the question is where, in the conforming class, "an instance of PhotoCollectionVC() is created, and the delegate property set to self". In viewDidLoad, that just creates another instance with an irrelevant delegate property that will never be called. The delegate property of the actual embedded PhotoCollectionVC needs to be assigned to self - in order for the two VCs to communicate. This is done from within the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
...
let controller = (segue.destinationViewController as! PhotoCollectionVC)
...
controller.delegate = self
}
}
}
The rest of the example code is fine.
Here is a super simple example of delegation from an embedded container to its delegate VC. The embedded container simply tells the VC that a button has been pressed. The story board is just a VC with a container in it and a text outlet. In the container VC, there is just a button. And the segue has an identifier.
The code in the delegate ViewController is:
protocol ChangeLabelText: class
{
func changeText()
}
class ViewController: UIViewController, ChangeLabelText
{
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
myLabel?.text = "Start"
}
func changeText()
{
myLabel?.text = "End"
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "feelTheBern"
{
let secondVC: myViewController = segue.destinationViewController as! myViewController
secondVC.delegate = self
}}
}
The code in the delegating View Controller, myViewController, is:
class myViewController: UIViewController
{
weak var delegate: ChangeLabelText?
#IBAction func myButton(sender: AnyObject)
{
print("action")
delegate?.changeText()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
i edited my question , because set textfield maybe can't be simple, need references so this is my code, but still have issue :
this code for TableViewController :
import UIKit
protocol PaymentSelectionDelegate{
func userDidSelectPayment(title: NSString?)
}
class PopPaymentTableViewController: UITableViewController {
var delegate : PaymentSelectionDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath?) {
self.dismissViewControllerAnimated(true){
if self.delegate != nil{
if let ip = indexPath{
var payItem : PayMethod
payItem = self.myList[ip.row] as! PayMethod
var title = payItem.paymentName
self.delegate.userDidSelectPayment(title)
}
}
}
}
}
and for code TransactionViewController :
import UIKit
class TransactionViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, PaymentSelectionDelegate, UITextFieldDelegate, UIPopoverPresentationControllerDelegate{
#IBOutlet var paymentTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
func resign(){
paymentTextField.resignFirstResponder()
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if (textField == paymentTextField){
resign()
performSegueWithIdentifier("seguePayment", sender: self)
}
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
func userDidSelectPayment(title: NSString?) {
paymentTextField.text = title as! String
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguePayment"{
var VC = segue.destinationViewController as! UINavigationController
var controller = VC.popoverPresentationController
if controller != nil{
controller?.delegate = self
var paymentVC = PopPaymentTableViewController()
paymentVC.delegate = self
}
}
}
}
this issue is: variable delegate in TableViewController like seems always nil, so cant set value
NB : sorry i edited totally my question, because so many answer say cant be set textfield just like that
The context of where your TransactionViewController is, is not completely clear, but you are instantiating a new ViewController. If you want to refer to an existing ViewController, this is not the instance you are using here. If you want to create a new one and show it after your didSelectRowAtIndexPath, you have to make sure, that the TextField is instantiated in the init-Method of your ViewController. As you are not instantiating it from a Storyboard, it seems that your TransactionViewController is created programmatically. Probably you are setting the TextField only in viewDidLoad or something else after the init().
You are trying to set text to paymentTextField which is still no initialized.
For this you have to set text to paymentTextField in viewDidLoad method in TransactionViewController.
remove transVC.paymentTextField.text = title from didSelectRowAtIndexPath
Add it in TransactionViewController's viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.paymentTextField.text = self.payName
}
It is not correct way to do that. You have not initialised text field and trying to set it's text. First initialise text field:
transVC.paymentTextField = UITextField()
Then try to do something.