-1

Hi guys I'm new to objective c and i was searching for an c++ or java like way to initialize an objects properties from another UIViewController class.

So I used the -init function which seems to work terrific, but the problem is that after some debugging I did I found that even though my property (an NSString) is successfully initialize in -init when the viewDidLoad starts it deletes (initialize) everything I did in init so I can't use my property! I want to play a video link but I found that the string that goes to playTheVideo method is null. Also note that when I initialize the stream inside viewDidLoad my video plays right away.

Here is my code:

ButtonsController.m

#import "ButtonsController.h"
#import "PlayVideoController.h"

@interface ButtonsController ()

@end

@implementation ButtonsController

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

-(void)createButton {

PlayVideoController *newObject = [[PlayVideoController alloc] initWithString:@"--somestring--" ];
[newObject playTheVideo];

}

PlayVideoController.m

#import "PlayVideoController.h"

@interface PlayVideoController ()

@end

@implementation PlayVideoController

@synthesize stream;
@synthesize player;


- (void)viewDidLoad
{
    [super viewDidLoad];

    [self playTheVideo];
}


- (id) initWithString: (NSString*) theStream {
    self = [super init];
    if (self) {
        stream = theStream;
    }
    return self;
}

- (void) playTheVideo {

    NSURL *url = [NSURL URLWithString:stream];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [player loadRequest:request];
}

PlayVideoController.h

#import <UIKit/UIKit.h>

@interface PlayVideoController : UIViewController

@property NSString *stream;

@property (weak, nonatomic) IBOutlet UIWebView *player;
- (void) playTheVideo;
- (id) initWithString: (NSString*) theStream;

@end

I create an object in main class just to start it quickly

EDIT: deleted actions in main.m and left the defaults

Mihir Oza
  • 2,768
  • 3
  • 35
  • 61
John Bassos
  • 308
  • 1
  • 10
  • Hasn't this got more to do with *view controller* classes than general objective-c classes? If so, you need to specify that. – trojanfoe Nov 12 '13 at 19:24
  • `when the viewdidload starts it deletes (initialises) everything i did in init` - highly unlikely. Is this your actual code? I don't see where you show a `PlayVideoController` anywhere. – Carl Veazey Nov 12 '13 at 19:34
  • yeap that's right. At least for the stream property. I really can't figure out what i'm doing wrong – John Bassos Nov 12 '13 at 19:36
  • i use main to create an object inside so it can start quickly just for the time. Shouldn't i? – John Bassos Nov 12 '13 at 19:42

3 Answers3

2

The issue is that you're creating this controller in your main function, but don't do anything with it and it will fall out of scope (and if using ARC, will be deallocated). Regardless, when the view controller is instantiated during the standard flow of the app, it will create a second instance, this time without having createButton called. So your calling of createButton in main is for naught. This concept of "I create an object in main class just to start it quickly" doesn't pass muster. The creating of the view controller in CreateButtons suffers from an analogous problem.

You really need to set the stream property somewhere else (e.g. in the viewDidLoad of PlayVideoController or in the prepareForSegue of the view controller that is segueing to PlayVideoController). But you almost certainly will never call initWithString for PlayVideoController (and you probably can retire that method).

If, for example, you're transitioning from ButtonsController view controller to PlayVideoController view controller via a segue with a storyboard identifier of, say playVideo, then you could use prepareForSegue in ButtonsController like so:

#import "PlayVideoController.h"

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([segue.identifier isEqualToString:@"playVideo"]){
        PlayVideoController *controller = (PlayVideoController *)segue.destinationViewController;
        controller.stream = @"--somestring--";
    }
}

(See the "Passing data forward using segues" section of this answer about passing data between controllers.)

If you want to do something at startup, e.g. initialize some model object structure (but not view controllers), you can do that in your app delegate's didFinishLaunchingWithOptions, but there's nothing shared with us thus far that seems to suggest that this is necessary. But if you do this, make sure you do the necessary memory management to keep it around (e.g. make this model object a property of the app delegate, make it a singleton, etc.). But if you create a local variable and configure it and let it fall out of scope, you'll lose the work you did on that.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Ok i changed it so i created a new viewController that uses the ButtonsController in my storyboard and added a simple button that does a modal segue when it's pressed to my PlayVideoController. Inside ButtonsController's viewdidload i called the createButton method and so it finished the same way – John Bassos Nov 12 '13 at 20:10
  • @user2984676 Yeah, your `createButton` method is yet another example of the same problem, that you're creating `PlayVideoController`, setting `stream`, but again not doing anything with this view controller, so it falls out of scope and is released. I was suggesting that you add a scene to your storyboard with `PlayVideoController` as it's view controller, and then set `self.stream` in `viewDidLoad` of `PlayVideoController`. Or alternatively, you can have `ButtonsController` have a segue to `PlayVideoController` and the `prepareForSegue` in `ButtonsController` can set the `stream` property. – Rob Nov 12 '13 at 20:17
  • But don't just instantiate a view controller with `initWithString` and then discard that view controller. You don't want to be calling `initWithString` of your `PlayVideoController`, but rather when you segue to that controller, you want to let storyboards do the necessary instantiation for you, and only set the `stream` property in `prepareForSegue`. – Rob Nov 12 '13 at 20:19
  • I have used this but i want to do this programmatically because i want to create many buttons that each one must send a string (with the url) to the PlayViewController. So it would cause a big mess in the storyboard i want to avoid – John Bassos Nov 12 '13 at 20:39
  • @user2984676 Two options: 1. Have single segue between the two scenes, and do `[self performSegueWithIdentifier:@"playVideo]`, and then use `prepareForSegue` as outlined above. 2. Programmatically instantiate new `PlayVideoController` using `PlayVideoController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"PlayVideoController"];` set the `stream` property with `controller.stream = @"...";` and then manually push/present as you see fit, e.g. `[self.navigationController pushViewController:controller ...];` – Rob Nov 12 '13 at 20:49
  • Great! I will check this out as soon as I get home – John Bassos Nov 12 '13 at 21:01
1

Your property should specify attributes for its memory management and whether it is atomic or nonatomic.

So instead of @property NSString *stream;

You should write

@property (copy, nonatomic) NSString *stream;

You no longer need to write @synthesize as that is taken care of for you.

Lastly, in your initializer you need to access the iVar directly by using the underscore.

So it should say _stream = [theStream copy];

EDIT:

Change your getter to self.stream

Peter Foti
  • 5,526
  • 6
  • 34
  • 47
  • i did that. The same happens – John Bassos Nov 12 '13 at 19:35
  • @Rob that is correct, updated answer to include that. Thanks for pointing that out. – Peter Foti Nov 12 '13 at 19:44
  • inside the init method? – John Bassos Nov 12 '13 at 19:44
  • @user2984676 Yes, you should set the property in `initWithString` method using the ivar directly (`_stream = [theStream copy]`), but if you ever change it elsewhere, you should always use the setter to update the property (`self.stream = someOtherStringValue`, and the setter will perform the `copy` for you). BTW, if you use this practice of omitting the `@synthesize` so the ivar becomes `_stream`, you'll have to change `playTheVideo` to also reference either `self.stream` or `_stream`. – Rob Nov 12 '13 at 19:48
  • used this in all my code. It didn't help. Perhaps there might be another function to call for init or some way to exclude viewdidload from the controller? – John Bassos Nov 12 '13 at 19:58
  • If its still not working then the code is elsewhere. If you set a break point after you set newObject you should see its iVar is set. – Peter Foti Nov 12 '13 at 20:00
  • @user2984676 This is not the issue with your code, though Peter's suggestion is, in general, good counsel. He's just showing you best practice with string properties (and he probably suggested it because failing to use `copy` can sometimes result in the sort of behavior you describe if the caller was using a `NSMutableString`). In this case, it's unrelated to your problem, but it is still, nonetheless, wise counsel. – Rob Nov 12 '13 at 20:09
0

You should update your PlayVideoController.h file as below

#import <UIKit/UIKit.h>

@interface PlayVideoController : UIViewController

@property (strong, nonatomic) NSString *stream;
@property (weak, nonatomic) IBOutlet UIWebView *player;

- (void) playTheVideo;
- (id) initWithString: (NSString*) theStream;

@end

It should work.

  • Properties are strong by default, so I don't think supplying `strong` is the issue (but it's better, IMHO, to specify it unambiguously, like you have here). As Peter points out, though, `copy` is generally better with strings, that way you don't have to worry about a scenario where the caller used a `NSMutableString` that may have mutated on you behind your back. – Rob Nov 12 '13 at 19:54