Reputation: 212
I currently have a UIPageViewController set up in my project almost exactly like the default page-based application template.
However, in the init method for my ModelController I am using NSURLConnection to async download data into an array (of images) that is supposed to be displayed on the PageViewController.
That means when my root view controller goes and inits a starting view controller the resources might not be downloaded yet and then the model controller is fetching things from an empty array which crashes the app.
How can I implement a safe way to show the images in a PageView? I was thinking of using an empty view controller with an activity indicator as the starting view controller but I don't know how I'd then let the model controller know when the download is completed so I can then update the views with the images.
my root view controller (this is the uipageviewcontroller delegate)
@interface CSAPromoViewController ()
@property (readonly, strong, nonatomic) CSAPromoModelController *modelController;
@end
@implementation CSAPromoViewController
@synthesize modelController = _modelController;
- (void)viewDidLoad
{
[super viewDidLoad];
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
CSAPageDataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
//set page view controller's bounds
CGRect pageViewRect = self.view.bounds;
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
my model controller (this is the data source)
@interface CSAPromoModelController()
@property (readonly, strong, nonatomic) NSArray *promosArray;
@end
@implementation CSAPromoModelController
-(id)init
{
self = [super init];
if (self) {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://blah.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
_promosArray = [self parseJSON:data];
}];
}
return self;
}
- (CSAPageDataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
// Return the data view controller for the given index.
if (([self.promosArray count] == 0) || (index >= [self.promosArray count] / 2)) {
return nil;
}
// Create a new view controller and pass suitable data.
CSAPageDataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:@"CSAPageDataViewController"];
dataViewController.promoOne = [self.promosArray objectAtIndex:index * 2];
dataViewController.promoTwo = [self.promosArray objectAtIndex:(index * 2) + 1];
return dataViewController;
}
the data view controller
@implementation CSAPageDataViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.promoLabelTop.text = [self.promoOne name];
self.promoImageTop.image = [self.promoOne image];
self.promoLabelBottom.text = [self.promoTwo name];
self.promoImageBottom.image = [self.promoTwo image];
}
Upvotes: 0
Views: 991
Reputation: 19114
Your problem you're trying to solve is an asynchronous one. Your approach however is for solving a synchronous problem.
For example, your class CSAPromoModelController
is inherently asynchronous. This is because it's init method invokes an asynchronous method, and thus your class gets "infected" by asynchronism.
You might consider a re-design, where class CSAPromoModelController
becomes a subclass of NSOperation
with a complete handler, e.g. CSAPromoModelOperation
. It's eventual result is the array of images. The imageArray becomes an ivar of your CSAPromoViewController
. The CSAPromoViewController
will have a method for creating a CSAPromoModelController
object which will be initialized with an image. The completion handler of the operation passes the array of images. Within the completion handler you basically execute the same statements as in your original viewDidLoad
method in order to setup the controllers.
You would use the operation as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
NSURLRequest *request = ...
CSAPromoModelOperation* op =
[CSAPromoModelOperation alloc] initWithRequest:request
completion:^(NSArray* result, NSError*error)
{
// assuming we are executing on the main thread!
if (error == nil) {
self.imageArray = result;
CSAPageDataViewController* startingViewController =
[self viewControllerWithImage:self.imageArray[0]
storyboard:self.storyboard];
NSArray* viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
...
}
else {
// handle error
}
}];
[op start];
}
Upvotes: 1