How to pass values using KVO?
Asked Answered
D

3

6

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
Doormat answered 11/8, 2017 at 18:12 Comment(2)
What is the output of the logs?All
@AminNegm-Awad please see the log output postedDoormat
A
3

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.

Attempt answered 16/8, 2017 at 6:56 Comment(2)
but I want to pass the value using KVO as state din the. questionDoormat
KVO is an Observer pattern, you are not passing value using KVO, you are receiving value changes using it. It's important to understand. The answer is correct. You've created a new instance of TruckViewController but it is not displayed on the UI and that's why your textField is empty. Try the solution in this answer.Telethon
B
1
  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

Bleareyed answered 11/8, 2017 at 21:37 Comment(0)
L
1

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];
    }
}
Latinize answered 12/8, 2017 at 3:10 Comment(2)
i placed [self.textFieldCurrentSpeed setText:self.receivedCurrentSpeed]; inside observevalueforkey but nothing changed. also I tried to remove the observer some where else, but I could not because I have to declare instance of the class TrcuckViewController globally, and I do not know how to do it..would you please help meDoormat
@user2121 I'm sorry I did not notice your comment, I have update the answer, hope will help you.Latinize

© 2022 - 2024 — McMap. All rights reserved.