Reload and not reload if press back from different view controllers. Swift
Asked Answered
N

3

7

The top three answers can solve my questions. It is hard to pick which one is the best. So, I just pick the one who is the first to answer my question. Sorry for amateur and iOSEnthusiatic. Thank you for your help. I appreciate it.

enter image description here

ViewController 1 has a table view.

My question is how to reload the table view only if I click back from view controller 2, and not reload the table view if I click back from view controller 3.

Right now, my code for back button is

@IBAction func backButtonTapped(sender: AnyObject) {
    self.dismissViewControllerAnimated(true, completion: nil)
}

In view controller 1. I know that the table view would be reloaded from either view controller 2 or 3

override func viewDidAppear(animated: Bool) {
    loadTable()
}

I tried to put loadTable() in viewDidLoad and try to write the below code for back button in view controller 2. But, it doesn't work.

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("UserHomePageViewController") as! UserHomePageViewController
controller.viewDidLoad()

Any suggestion what I should do? Thank you for your help.

EDIT:

I think this is an easier way to do it, but it still does not work as I thought. I guess it is because the viewDidAppear is executed before the call of reloadTableBool. Correct? Is there any way to fix it? Thank you. You help would be appreciated.

class 2ViewController
@IBAction func backButtonTapped(sender: AnyObject) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewControllerWithIdentifier("1ViewController") as! 1ViewController
        print("viewcontroller2 before call: \(controller.reloadTableBool)")
        controller.reloadTableBool = false
        print("viewcontroller2 after call: \(controller.reloadTableBool)")
        self.dismissViewControllerAnimated(true, completion: nil)
    }
class 1ViewController
var reloadTableBool = true
override func viewDidAppear(animated: Bool) {
    print("viewcontroller1: \(reloadTableBool)")
    if reloadTableBool == true {
        loadTable()
    }
}

When I click back on view controller 2, it prints

viewcontroller2 before call: true
viewcontroller2 after call: false
viewcontroller1: true
Norbertonorbie answered 24/1, 2016 at 4:38 Comment(7)
Are you using the back button you get from the navigation controller or did you created one and added it manually?Sauder
I drag a Bar Button Item to navigation bar and create a function for that button as "back". I guess I create it manually.Norbertonorbie
In your screenshots, it shows you're using a navigation controller. Why don't you use the build in back button?Sauder
Um. Not sure how to use the build in one. LOLNorbertonorbie
Eh, it's kinda automatic :p Either way, I've updated my answer.Sauder
Ya. Haha. I know. I am still thinking why sometime the build-in back button shows up. But, sometimes I can't see it. That's why I just create my own. Thank you for your help first. I will read the code and try to apply it tomorrow morning.Norbertonorbie
Additional comment regarding your latest attempt : when you call storyboard.instantiateViewControllerWithIdentifier from backButtonTapped, the returned controller is not the same instance you were using before. instantiateViewControllerWithIdentifier will always create a new controller, so you are not communicating with your first VC as you thought you were.Lithograph
L
2

If displaying vc2 is performed by vc1 and is always sure to invalidate the data in vc1, you could do the following:

  • add a needsReload boolean instance variable to vc1
  • set it to true whenever you display vc2 (and when instanciating vc1 eg in awakeFromNib if coming from a storyboard)
  • only perform the content of loadTable if needsReload is true (maybe refactor this logic into a loadTableIfNeeded)
  • don't forget to set needsReload to false in the end of loadTableIfNeeded

This invalidation pattern is found throughout UIKit, see for example UIView setNeedsLayout/layoutIfNeeded. The advantage is that even if several events cause the data to invalidate, it will only actually get refreshed when you need it.

In your situation it has the additional advantage of keeping the logic contained in vc1 and not creating unnecessary coupling between your VCs, which is always good.

---UPDATE: sample implementation (ObjC but you'll get the idea)

You only need to handle this in VC1, forget about all the back button stuff in VC2. This implementation will mark VC1 for reload as soon as VC2 is presented, but will actually reload only on viewWillAppear, when VC2 is dismissed.

---UPDATE 2: Added a conditional reload based on a delegate callback

Note that _needsReload is now set in the delegate callback, not when VC2 is first presented. Instead we set VC1 as the delegate of VC2. (_needsReload logic is actually unnecessary using this method, kept it for reference)

//VC2: add a delegate to the interface

@class VC2;

@protocol VC2Delegate
- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges;
@end

@interface VC2
@property (nonatomic, weak) id<VC2Delegate> delegate
@end

@implementation VC2 

- (IBAction) finishWithChanges
{
   [self.delegate viewController:self didFinishEditingWithChanges:YES];
}

- (IBAction) finishWithoutChanges
{
   [self.delegate viewController:self didFinishEditingWithChanges:NO];
}

@end

//VC1: implement the VC2Delegate protocol

@interface VC1 () <VC2Delegate>
@end

@implementation VC1
{
   BOOL _needsReload
}

- (void) awakeFromNib 
{
   //adding this for completeness but the way you did it in Swift (at init) is correct
   [super awakeFromNib];
   _needsReload = YES;
}

- (void) viewWillAppear:(BOOL)animated
{
   [super viewWillAppear:animated];
   [self reloadTableIfNeeded];
}

- (IBAction) displayVC2
{
   VC2* myVC2 = ... //instanciate your VC2 here
   myVC2.delegate = self; //set as the delegate of VC2
   [self presentViewController:myVC2 animated:YES completion:nil];
}

- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges
{
   _needsReload = hasChanges;
   [self reloadTableIfNeeded];
}

- (void) reloadTableIfNeeded
{
   if (_needsReload) {
      [self.tableView reloadData];
      _needsReload = NO;
   }
}

@end
Lithograph answered 24/1, 2016 at 9:12 Comment(11)
I did that too but it seems viewDidAppear is executed before the call of reloadTableBool. Would you mind to take a look on my update?Norbertonorbie
I got it. Work. Thank you for your help:)Norbertonorbie
Good news. Would have been even better news if you had accepted my answer, but still :-)Lithograph
One more question. If I want to assign a value from display2 in order to tell display1 whether the table view should reload like what I try to code in EDIT, is it possible?Norbertonorbie
Yes it is possible, for example using the delegate pattern. See my update. Hope you won't have too much trouble Swifting it.Lithograph
It is because in display2, I do not want to reload table view in display1 if I press back button, but I do want to reload the table view if I press "send" button (a new button in display2)Norbertonorbie
I can understand your update. But if I have two buttons in display2, one is needed to reload the table view in display and another does not need to. What can I do?Norbertonorbie
If I follow your code, both button will reload the table view or both button will not reload the table view. Correct?Norbertonorbie
Updated the VC2 implementation to address your exact case, this should set you up enough to move forward with your own implementation.Lithograph
I do not quite understand these codes in objective c: didFinishEditingWithChanges Is that same as fun didFinishEditingWithChanges ( not sure ) -> Bool { not sure } and (nonatomic, weak) id<VC2Delegate> delegate No idea about this. Sorry for bother you.Norbertonorbie
See "Delegation" in the apple documentation of protocols in Swift developer.apple.com/library/ios/documentation/Swift/Conceptual/… You should be familiar with delegation implementation before attempting anything serious in Cocoa. I'll let you figure out the exact Swift syntax, which I don't know. (You can take UIKit classes that implement delegates as models, for example UITableView)Lithograph
S
3

Here is a link to a question I answered a couple days ago. Use the navigation controller delegate to handle the back button. In your second view controller, set the delegate to self and reload the tableview when you press the back button.

override func viewDidLoad() {
    super.viewDidLoad()

    navigationController?.delegate = self
}

func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    if let controller = viewController as? FirstViewController {
        controller.tableView.reloadData()
    }
}

NOTE: I'm assuming you're using the back button of the navigation controller here.

EDIT: Another example using your manually added back button:

@IBAction func backButtonTapped(sender: AnyObject) {

    if let viewControllers = app.window?.rootViewController?.childViewControllers {
        viewControllers.forEach { ($0 as? FirstViewController)?.tableView.reloadData() }
    }

    self.dismissViewControllerAnimated(true, completion: nil)
}

Seeing as you are using a navigation controller:

@IBAction func backButtonTapped(sender: AnyObject) {

    navigationController?.viewControllers.forEach { ($0 as? FirstViewController)?.tableView.reloadData() }

    self.dismissViewControllerAnimated(true, completion: nil)
}
Sauder answered 24/1, 2016 at 4:49 Comment(4)
Hi. Would you mind to see my edit? I think it is easier way to do it as I can just use a Bool, but it seems viewDidAppear is executed before the call of reloadTableBool?Norbertonorbie
Actually my approach should be the easiest and arguably cleanest compared to having have to keep in mind to send a Bool every time you navigate back to your first view. Did any of my suggestions worked? I'll look at your edit now.Sauder
The reason your Bool doesn't work is because you're creating a new instance of UserHomePageViewController and changing things only for that instance. What you want is to change the Bool in your existing instance of UserHomePageViewController. Use my examples and if you insist on using a Bool, then replace the table view reload with a Bool.Sauder
Opps. UserHomePageViewController is class 1viewcontroller. I forgot to change it. I just want to make my code easier to read. LOL. I am looking your code now.Norbertonorbie
L
2

If displaying vc2 is performed by vc1 and is always sure to invalidate the data in vc1, you could do the following:

  • add a needsReload boolean instance variable to vc1
  • set it to true whenever you display vc2 (and when instanciating vc1 eg in awakeFromNib if coming from a storyboard)
  • only perform the content of loadTable if needsReload is true (maybe refactor this logic into a loadTableIfNeeded)
  • don't forget to set needsReload to false in the end of loadTableIfNeeded

This invalidation pattern is found throughout UIKit, see for example UIView setNeedsLayout/layoutIfNeeded. The advantage is that even if several events cause the data to invalidate, it will only actually get refreshed when you need it.

In your situation it has the additional advantage of keeping the logic contained in vc1 and not creating unnecessary coupling between your VCs, which is always good.

---UPDATE: sample implementation (ObjC but you'll get the idea)

You only need to handle this in VC1, forget about all the back button stuff in VC2. This implementation will mark VC1 for reload as soon as VC2 is presented, but will actually reload only on viewWillAppear, when VC2 is dismissed.

---UPDATE 2: Added a conditional reload based on a delegate callback

Note that _needsReload is now set in the delegate callback, not when VC2 is first presented. Instead we set VC1 as the delegate of VC2. (_needsReload logic is actually unnecessary using this method, kept it for reference)

//VC2: add a delegate to the interface

@class VC2;

@protocol VC2Delegate
- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges;
@end

@interface VC2
@property (nonatomic, weak) id<VC2Delegate> delegate
@end

@implementation VC2 

- (IBAction) finishWithChanges
{
   [self.delegate viewController:self didFinishEditingWithChanges:YES];
}

- (IBAction) finishWithoutChanges
{
   [self.delegate viewController:self didFinishEditingWithChanges:NO];
}

@end

//VC1: implement the VC2Delegate protocol

@interface VC1 () <VC2Delegate>
@end

@implementation VC1
{
   BOOL _needsReload
}

- (void) awakeFromNib 
{
   //adding this for completeness but the way you did it in Swift (at init) is correct
   [super awakeFromNib];
   _needsReload = YES;
}

- (void) viewWillAppear:(BOOL)animated
{
   [super viewWillAppear:animated];
   [self reloadTableIfNeeded];
}

- (IBAction) displayVC2
{
   VC2* myVC2 = ... //instanciate your VC2 here
   myVC2.delegate = self; //set as the delegate of VC2
   [self presentViewController:myVC2 animated:YES completion:nil];
}

- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges
{
   _needsReload = hasChanges;
   [self reloadTableIfNeeded];
}

- (void) reloadTableIfNeeded
{
   if (_needsReload) {
      [self.tableView reloadData];
      _needsReload = NO;
   }
}

@end
Lithograph answered 24/1, 2016 at 9:12 Comment(11)
I did that too but it seems viewDidAppear is executed before the call of reloadTableBool. Would you mind to take a look on my update?Norbertonorbie
I got it. Work. Thank you for your help:)Norbertonorbie
Good news. Would have been even better news if you had accepted my answer, but still :-)Lithograph
One more question. If I want to assign a value from display2 in order to tell display1 whether the table view should reload like what I try to code in EDIT, is it possible?Norbertonorbie
Yes it is possible, for example using the delegate pattern. See my update. Hope you won't have too much trouble Swifting it.Lithograph
It is because in display2, I do not want to reload table view in display1 if I press back button, but I do want to reload the table view if I press "send" button (a new button in display2)Norbertonorbie
I can understand your update. But if I have two buttons in display2, one is needed to reload the table view in display and another does not need to. What can I do?Norbertonorbie
If I follow your code, both button will reload the table view or both button will not reload the table view. Correct?Norbertonorbie
Updated the VC2 implementation to address your exact case, this should set you up enough to move forward with your own implementation.Lithograph
I do not quite understand these codes in objective c: didFinishEditingWithChanges Is that same as fun didFinishEditingWithChanges ( not sure ) -> Bool { not sure } and (nonatomic, weak) id<VC2Delegate> delegate No idea about this. Sorry for bother you.Norbertonorbie
See "Delegation" in the apple documentation of protocols in Swift developer.apple.com/library/ios/documentation/Swift/Conceptual/… You should be familiar with delegation implementation before attempting anything serious in Cocoa. I'll let you figure out the exact Swift syntax, which I don't know. (You can take UIKit classes that implement delegates as models, for example UITableView)Lithograph
A
1

You can use notification approach easily for this.

Add observer in your 1st ViewController in viewDidLoad method.

NSNotificationCenter.defaultCenter().addObserver(self, selector: "reloadTable:", name: "reloadTable", object: nil)

func reloadTable(notification : NSNotification){


let isReload : NSNumber = notification.userInfo!["isReload"] as! NSNumber

    if (isReload.boolValue) {
        self.tableView.reloadData()
    }
}

Then post notification like this from your 2nd and 3rd ViewController respectively when you call dismissViewController.

// From 2nd viewcontroller

NSNotificationCenter.defaultCenter().postNotificationName("reloadTable", object: nil, userInfo: ["isReload" : NSNumber(bool: false)])

// From 3rd viewcontroller
NSNotificationCenter.defaultCenter().postNotificationName("reloadTable", object: nil, userInfo: ["isReload" : NSNumber(bool: true)])
Atrice answered 24/1, 2016 at 6:58 Comment(4)
No! you should use this approach to achieve desired result.Atrice
UM. Sorry. I am not quite understand objective CNorbertonorbie
Sorry, its reloadData(). Please check answer now. ThanksAtrice
I got two errors. 1. unrecognized selector sent to instance 0x7fb61283a000 2. Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-Norbertonorbie

© 2022 - 2024 — McMap. All rights reserved.