Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass values using KVO?

I am learning how to use KVO. I created two classes, Truck and Driver as shown below.

the Truck class has a textfield and a button, the text should contain the current truck speed, and when the button is pressed, prepareForSegue should be called and it contains the code posted below.

the Driver class contain a textfield that should be filled with the truck speed. the truck speed in Truck class will be passed to the textfield in Driver class through KVO as shown in the code.

the problem I have or what I am trying to do is, when the user enters a speed of the truck in the Truck class and presses the button, I want to display the truck speed entered in the textfield in the Driver class through KVO

the result I am getting according to the code posted below is, an empty textfield in the Driver class

please let me know why the textfield in the driver class is empty. and how should I pass the value of the truck speed from Truck class to Driver class through KVO

Truck.m

#import "TruckViewController.h"
#import "DriverViewController.h"
#import "ServerViewController.h"

@interface TruckViewController ()

@property (strong, nonatomic) IBOutlet UITextField *textFieldCurrenSpeed;
@property (strong, nonatomic) IBOutlet UIButton *buttonBroadcast;
@property (strong, nonatomic) IBOutlet UIButton 
*buttonToDriverViewController;
@property (strong, nonatomic) IBOutlet UIButton   
*buttonToServerViewController;
@property (strong, nonatomic) TruckViewController *truck;
@property (strong, nonatomic) DriverViewController *driver;
@property (strong, nonatomic) ServerViewController *server;

@end


@implementation TruckViewController


- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}


 - (void)didReceiveMemoryWarning {
 [super didReceiveMemoryWarning];
 // Dispose of any resources that can be recreated.
 }


-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
{
 self.truck = [[TruckViewController alloc] init];
 self.driver = [[DriverViewController alloc] init];
 self.server = [[ServerViewController alloc] init];

if ([segue.identifier isEqualToString:@"segueToDriver"]) {

    [self.truck addObserver:self.driver
            forKeyPath:@"currentSpeedOfTheTruck"
               options:NSKeyValueObservingOptionNew
               context:NULL];

    self.truck.currentSpeedOfTheTruck = [self.textFieldCurrenSpeed 
text];
    NSLog(@"prepareForSegue: %@", self.truck.currentSpeedOfTheTruck);
}

if ([segue.identifier isEqualToString:@"segueToServer"]) {

    [self.truck addObserver:self.server
            forKeyPath:@"currentSpeedOfTheTruck"
               options:NSKeyValueObservingOptionNew
               context:NULL];

    self.truck.currentSpeedOfTheTruck = [self.textFieldCurrenSpeed 
 text];
    NSLog(@"text entered: %@", self.truck.currentSpeedOfTheTruck);

 }

}

- (void)dealloc
{
[self.truck removeObserver:self.driver 
forKeyPath:@"currentSpeedOfTheTruck"];
[self.truck removeObserver:self.server    
forKeyPath:@"currentSpeedOfTheTruck"];
}

Driver.m

  #import "DriverViewController.h"

   @interface DriverViewController ()
   @property (strong, nonatomic) IBOutlet UITextField   
   *textFieldCurrentSpeed;

 @end


 -(void) observeValueForKeyPath:(NSString *)keyPath
                  ofObject:(id)object
                  change:(NSDictionary<NSKeyValueChangeKey,id> 
 *)change
                   context:(void *)context {

 if ([keyPath isEqualToString:@"currentSpeedOfTheTruck"]) {
    NSLog(@"DriverViewController->currentSpeedOfTheTruck: %@",
          [object valueForKey:keyPath]);

    NSLog(@"DriverViewController->currentSpeedOfTheTruck: %@",
          [change objectForKey:NSKeyValueChangeNewKey]);

    self.receivedCurrentSpeed = [change   
 objectForKey:NSKeyValueChangeNewKey];
    [self.textFieldCurrentSpeed setText:self.receivedCurrentSpeed];

 }
 }



  - (void)viewDidLoad {
   [super viewDidLoad];
  // Do any additional setup after loading the view.

   NSLog(@"DriverViewController->viewDidLoad");

   [self.textFieldCurrentSpeed setText:self.receivedCurrentSpeed];
   }

  - (void)didReceiveMemoryWarning {
   [super didReceiveMemoryWarning];
   // Dispose of any resources that can be recreated.
 }

log output:

2017-08-13 19:53:55.293 KVC-1[91529:2346883] DriverViewController-  
>currentSpeedOfTheTruck: 45

2017-08-13 19:53:55.294 KVC-1[91529:2346883] DriverViewController- 
>currentSpeedOfTheTruck: 45
2017-08-13 19:53:55.294 KVC-1[91529:2346883] prepareForSegue: 45
like image 389
user2121 Avatar asked Aug 11 '17 18:08

user2121


3 Answers

  1. It appears that you're setting the text content of textFieldCurrentSpeed in -viewDidLoad and nowhere else, so it's not surprising that it never changes after the view is loaded.

  2. The pattern for using -observeValueForKeyPath:object:change:context: is incorrect. You need to use a context variable, check for that, and call super's implementation if it doesn't match. See: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOBasics.html

like image 34
Charles Srstka Avatar answered Oct 09 '22 18:10

Charles Srstka


The problem is with these lines of code

 self.truck = [[TruckViewController alloc] init];
 self.driver = [[DriverViewController alloc] init];
 self.server = [[ServerViewController alloc] init];

Since your creating another object rather than one is created before preparing for segue, you should use segue.destinationController to transfer the values

Change the above line of code to

 self.truck = (TruckViewController *) segue.destinationViewController;

Accordingly to all viewcontrollers.

like image 146
Saranjith Avatar answered Oct 09 '22 18:10

Saranjith


There is two mistake:

  1. In truck you add observer driver, but then you removed it immediately, so in driver you can't receive notify of the value change for currentSpeedOfTheTruck. remove it when you don't need observer value changes, but must before driver dealloc.
  2. When you receive notify of currentSpeedOfTheTruck, you only set value for receivedCurrentSpeed, not self.textFieldCurrentSpeed.text, so the text of self.textFieldCurrentSpeed is empty. Move [self.textFieldCurrentSpeed setText:receivedCurrentSpeed] to func observeValueForKeyPath: or override setter of receivedCurrentSpeed

Truck.m

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {    
    if ([segue.identifier isEqualToString:@"segueToServer"]) {
        if ([segue.destinationViewController isKindOfClass:[DriverViewController class]]) {
            ((DriverViewController *)segue.destinationViewController).sourceViewController = self;

            [self addObserver:segue.destinationViewController forKeyPath:@"currentSpeedOfTheTruck" options:NSKeyValueObservingOptionNew context:nil];
        }
    }
}

Driver.h

@property (nonatomic, weak) TrunkViewController *sourceViewController;

Driver.m

- (void)dealloc {
    [self.sourceViewController removeObserver:self forKeyPath:@"currentSpeedOfTheTruck"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"currentSpeedOfTheTruck"]) {
        id newValue = [change objectForKey:NSKeyValueChangeNewKey];
        if ([newValue isKindOfClass:NSString.class]) {
            self.receivedCurrentSpeed = newValue
            [self.textFieldCurrentSpeed setText:self.receivedCurrentSpeed];
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}
like image 1
Zhang Avatar answered Oct 09 '22 16:10

Zhang