Popcorn
Popcorn

Reputation: 5348

Why is my object not being passed correctly via segue?

Say I have 2 controllers, BarViewController and FooViewController.

FooViewController has an outlet to a UIImageView called imageView:

@property (nonatomic, weak) UIImageView *imageView;

BarViewController has an outlet to a UIButton button. BarViewController has a segue from this button to FooViewController, called BarToFooSegue (done in storyboard).

When I run the following code, and call NSLog on FooViewController.imageView.image, the result is nil, and my image won't display. Why is this the case?

// code in BarViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

    if([segue.identifier isEqualToString:@"BarToFooSegue"]){
        NSURL *photoUrl = @"http://www.randurl.com/someImage"; // assume valid url
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:photoUrl]];
        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];

        [segue.destinationViewController setImageView:imageView];
    }
}

I've tried setting FooViewController.imageView to strong instead of weak, but the problem remains:

@property (nonatomic, strong) UIImageView *imageView;

Running my debugger, I notice the imageView in FooViewController is updated correctly inside prepareForSegue: but then gets re-updated a few lines afterwards to some newly allocated imageView with @property image set to nil. I am not sure what part of the control flow is causing this because it happened in lines written in assembly language.

I got my code working by adding a UIImage property to FooViewController:

@property (nonatomic, strong) UIImage *myImage;

and changing prepareForSegue: in BarViewController to pass the image instead of the imageView:

// code in BarViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

    if([segue.identifier isEqualToString:@"BarToFooSegue"]){
        NSURL *photoUrl = @"http://www.randurl.com/someImage"; // assume valid url
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:photoUrl]];

        [segue.destinationViewController setMyImage:image];
}

and modifying viewWillAppear: in FooViewController:

- (void)viewWillAppear:(BOOL)animated{
    [self.imageView setImage:self.myImage];
}

Upvotes: 4

Views: 1266

Answers (4)

danh
danh

Reputation: 62676

I agree with @ikuragames answer if you must set the imageView.image directly. But typically, I keep my ViewControllers' view hierarchies private.

I don't think it's ugly to add that UIImage property to Foo. I think it's actually prettier than @ikuragames correct solution.

Upvotes: 1

ikuramedia
ikuramedia

Reputation: 6058

Call [segue.destinationViewController view]; before you set the image, this will cause the view hierarchy to be loaded and your outlets will then be set.

Upvotes: 6

Zaraki
Zaraki

Reputation: 3740

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

    if([segue.identifier isEqualToString:@"BarToFooSegue"]){
        NSURL *photoUrl = @"http://www.randurl.com/someImage"; 
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:photoUrl]];

   
        FooViewController *vc = [segue destinationViewController];
        vc.myImage = image;   
 }
}

Upvotes: -1

Scott Berrevoets
Scott Berrevoets

Reputation: 16946

In prepareForSegue outlets aren't defined yet--they're nil. You're sending a message to nil, which is perfectly fine and therefore doesn't give you an error, but in this case it can lead to unexpected behavior. You can solve it by creating a temporary UIImage property, and set your image view to that image in viewDidLoad or viewWillAppear.

Upvotes: 2

Related Questions