I am writing an iOS6 app using Cocos2D with ARC turned on (Cocos is linked as a static library, not under ARC). I am able to present the camera using the following code:
cameraController = [[UIImagePickerController alloc] init];
// set other properties of camera
cameraController.delegate = psImageLayer;
psImageLayer.imagePicker = cameraController;
[[CCDirector sharedDirector] presentViewController:cameraController animated:YES completion:nil];
and I dismiss the camera in psImageLayer with this code:
- (void) imagePickerController: (UIImagePickerController *) picker
didFinishPickingMediaWithInfo: (NSDictionary *) info {
// do something with image
[[CCDirector sharedDirector] dismissViewControllerAnimated:YES completion:nil];
}
When I dismiss the camera, the app crashes with the following error: *** -[PLImageScrollView release]: message sent to deallocated instance 0x2494f4f0 I am pretty sure that PLImageScrollView is an iOS class, because I did not write it.
My issue appears to be very similar to the issue posted here, but his solution involves modifying the class that owns the delegate. In this case, UIImagePickerController is the class, which cannot be modified.
The relevant parts of the PhotoShareImageLayer header file are posted below:
// PhotoShareImageLayer.h (this is what psImageLayer is)
#interface PhotoShareImageLayer : CCLayer <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
#property(nonatomic, retain) UIImagePickerController *imagePicker;
#property(nonatomic, retain) UIImage *currentImage;
#end
Any ideas on how to stop this error from happening? Thanks.
EDIT: List of things I have already tried.
Subclassing UIImagePickerController and adding - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self setDelegate:nil];
}
EDIT 2: The crash does not happen in imagePickerController:didCancel. Only when a picture is taken, OR when "Retake" is pressed in the camera. There is no UIImagePickerDelegate method for "Retake" (only "Cancel" and "Use").
EDIT 3: After continuing and writing more of the app, it appears this issue is not unique to the camera. The same (or very similar) errors occur when dismissing modal views for Twitter, Facebook, Contacts, and more.
I believe this is a problem with Apple's internal implementation of PLImageScrollView. I'm swizzling the UIScrollView`s setDelegate method and this causes a crash when a UIImagePicker is used (although, it only seems to be if you select a photo, not if you cancel). The problem I end up seeing is that the scrollViewDidScroll: method is sent to the real delegate (through my interceptor), but it's already been released.
This suggests to me that the PLImageScrollView's delegate is being dealloced without being nilled out. I half-solved the problem by creating my own strong reference to the real delegate. This could cause a memory leak in other implementations, but that's better than a crash at least.
Related
EDIT: It is appearing that there may not be ANY possible way to, on app termination only, set the device volume back to the level it was when an app started. To me this is a possible oversight on the part of Apple. Why wouldn't Apple want my app to be a good camper that leaves their campsite the way they found it, there must be a way, please help... I tried to get an answer to this broader question with another topic but it was closed as a duplicate, please go there and vote to re-open it :)
When my app terminates, I want to set the system volume back to the same volume it was when my app started (Edit: and not change the volume when the app enters the background).
I am attempting to do that with MPVolumeView as the mechanism to set the devices volume. From what I have researched, that seems to be the only way to set the devices volume programmatically. If there is another way, please suggest it.
So, when I launch my app I save the system volume as an external variable 'appStartVol' in my AppDelegate.m.
I let the user change the volume during app usage with MPVolumeView.
Then I try to set the system volume back to the appStartVol in the AppDelegate.m files' applicationWillTerminate. EDIT: applicationWillTerminate is called when a user dismisses apps from their recents list. I do this all the time and leave regularly used apps in recents so I don't have to scroll through 9 pages of icons to find them. So, there is a reason to do what I am asking, in that function.
I use this approach for screen brightness but can not seem to do it for volume.
I am having trouble because I do not seem to be able to get access to my storyboard MPVolumeView in AppDelegate.m applicationWillTerminate and I can not seem to make a local MPVolumeView work in AppDelegate.m applicationWillTerminate.
If I use a UIApplicationWillTerminateNotification notification in my view controller, I still run into the same problems in the notification event since the storyboard MPVolumeView also seems not accessible from that event.
EDIT: This is the reason that using code in applicationDidEnterBackground does not meet my needs: I want my users to be able to use my music player at the volume they have manually chosen in my app even when they decide to bring another app into focus. I believe that this is what the users would naturally assume would happen. For instance, why would the volume change if I want to use the calculator? I also want to believe that the natural assumption for a user would be that the volume should pop back to pre-app volume if the app is dismissed. Using applicationDidEnterBackground would make the app go to pre-app volume both when the app goes into background AND when it terminates, this is not acceptable.
ATTEMPT 1: Here is my code in my AppDelegate.m applicationWillTerminate:
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(#"=== App is terminating ===");
UIScreen.mainScreen.brightness = brightnessORG;
NSLog(#"=== Screen brightness back to pre-app level of %f ===", brightnessORG);
UISlider *volumeViewSlider;
for (UIView *view in [_mpVolumeViewParentView subviews]){
if ([view.class.description isEqualToString:#"MPVolumeSlider"]){
volumeViewSlider = (UISlider*)view;
volumeViewSlider.value = appStartVol;
break;
}
}
}
ATTEMPT 1: Here is my AppDelegate.h:
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
extern CGFloat brightnessORG;
extern float appStartVol;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
MPVolumeView *_mpVolumeViewParentView;
}
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic, retain) IBOutlet MPVolumeView *mpVolumeViewParentView;
#end
Re. ATTEMPT 1: Although this code runs without error, it does not set the volume back to appStartVol since there are no views in the app delegates [_mpVolumeViewParentView subviews]. I am obviously not accessing the mpVolumeViewParentView that is in my storyboard.
ATTEMPT 2: Lets see if I can just add a local MPVolumeView in AppDelegate.m applicationWillTerminate:
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(#"=== App is terminating ===");
UIScreen.mainScreen.brightness = brightnessORG;
NSLog(#"=== Screen brightness back to pre-app level of %f ===", brightnessORG);
MPVolumeView *volumeView = [ [MPVolumeView alloc] init];
UISlider *volumeViewSlider;
volumeViewSlider = (UISlider*)volumeView;
volumeViewSlider.value = appStartVol;
}
Re. ATTEMPT 2: Runs with error = 'Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MPVolumeView setValue:]: unrecognized selector sent to instance'
But I have tried :), as you can see I am an objective-c newbie...
Any help would be appreciated :)
ATTEMPT 3: Try subviews in local MPVolumeView in applicationWillTerminate:
MPVolumeView *_mpVolumeViewParentView = [ [MPVolumeView alloc] init];
MPVolumeView *volumeView = [ [MPVolumeView alloc] init];
[_mpVolumeViewParentView addSubview:volumeView];
UISlider *volumeViewSlider;
for (UIView *view in [_mpVolumeViewParentView subviews]){
if ([view.class.description isEqualToString:#"MPVolumeSlider"]){
volumeViewSlider = (UISlider*)volumeView;
volumeViewSlider.value = appStartVol;
break;
}
}
Re. ATTEMPT 3: Runs with error at for loop initiation: 'Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MPVolumeView setValue:]: unrecognized selector sent to instance'
ATTEMPT 4: After adding AVfoundation framework to the project, I added this to my AppDelegate.m:
#import <AVFoundation/AVFoundation.h>
And I put these two lines to AppDelegate.m applicationWillTerminate:
AVAudioPlayer* wavplayer = [[AVAudioPlayer alloc] init];
wavplayer.volume = appStartVol;
Re. ATTEMPT 4: Runs with error at 'wavplayer.volume= appStartVol;': 'Thread 1: EXC_BAD_ACCESS (code=1, address=0x48)' , darn......
First, you are trying something that probably can't work - you are casting MPVolumeView to UISlider and then try to use UISlider's setValue: method. But your object is still MPVolumeView and support that method. So this can't work not because you are using it in wrong place, but in never works.
Also - appWillTerminate is not sufficient, as it's called only in one specific case. If app goes to background first, and then killed - the willTerminate is never called.
I assume you were trying to de something like described here iOS 9: How to change volume programmatically without showing system sound bar popup? - but you should just do the same - so find the UISlider within subviews, instead of casting the whole thing to it.
Edit:
#implementation AppDelegate
{
MPVolumeView* mv;
UISlider* slider;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
slider.value = 1;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
mv = [MPVolumeView new];
for (UIView *view in [mv subviews]){
if ([view.class.description isEqualToString:#"MPVolumeSlider"]){
slider = ((UISlider*)view);
break;
}
}
}
#end
that's the sample code I used. Using in terminate unfortunately didn't work, so that's the most that I see that can be done (I also think it's the more correct way than using terminate, as it's not always called).
Remember this can stop working at any time, or even be rejected when submitted to the AppStore.
I am trying to make a class that plays YouTube videos, but I am having several problems with it.
Here is my class that handles YouTube videos:
// this is the only property declared in the .h file:
#property (strong, nonatomic) UIView * view
// the rest of this is the .m file:
#import "MyYouTube.h"
#interface MyYouTube()
#property (strong, nonatomic) NSDictionary * contentData;
#property (strong, nonatomic) UIWebView * webView;
#property (nonatomic) int videoOffset;
#end
#implementation MyYouTube
#synthesize view,contentData,webView,videoOffset;
- (MyYouTube*) initIntoView: (UIView*) passedView withContent: (NSDictionary*) contentDict {
NSLog(#"YOUTUBE: begin init");
self=[super init];
videoOffset=0;
view=[[UIView alloc] initWithFrame:passedView.bounds];
[view setBackgroundColor:[UIColor blackColor]];
[view setAutoresizesSubviews:YES];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
contentData=contentDict;
NSString * compiledUrl=[[NSString alloc] initWithFormat:#"http://_xxx_.com/app/youtube.php?yt=%#",[contentData objectForKey:#"cnloc"]];
NSURL * url=[[NSURL alloc] initWithString:compiledUrl];
NSURLRequest * request=[[NSURLRequest alloc] initWithURL:url];
webView=[[UIWebView alloc] initWithFrame:passedView.bounds];
[webView loadRequest:request];
[webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[[webView scrollView] setScrollEnabled:NO];
[[webView scrollView] setBounces:NO];
[webView setMediaPlaybackRequiresUserAction:NO];
[webView setDelegate:self];
NSLog(#"YOUTUBE: self: %#",self);
NSLog(#"YOUTUBE: delegate: %#",webView.delegate);
[view addSubview:webView];
NSLog(#"YOUTUBE: end init");
return self;
}
-(void)webViewDidFinishLoad:(UIWebView*)myWebView {
NSLog(#"YOUTUBE: send play command");
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"playVideo(%d)", videoOffset]];
}
- (void) dealloc {
NSLog(#"YOUTUBE: dealloc");
}
#end
Here is the code that calls this class (this code is located in the appDelegate):
NSLog(#"about to load youtube");
ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
NSLog(#"loaded youtube");
[mainView addSubview:[ytObj view]];
FYI mainView and ytObj are declared as this:
#property (nonatomic,strong) UIView * mainView;
#property (nonatomic,strong) MyYouTube * ytObj;
When this code is run, the app crashes and I get this in the console:
about to load youtube
YOUTUBE: begin init
YOUTUBE: self: <MyYouTube: 0x16503d40>
YOUTUBE: delegate: <MyYouTube: 0x16503d40>
YOUTUBE: end init
YOUTUBE: dealloc
loaded youtube
*** -[MyYouTube respondsToSelector:]: message sent to deallocated instance 0x166f19f0
If I set the UIWebView delegate to nil then the app doesn't crash, but, as expected, the YouTube video doesn't autoplay.
Can anyone explain to me:
Why does the object deallocates immediately after it has been
initialized?
Why the respondsToSelector: message is sent to an
instance other than the MyYouTube one?
How can I get the YouTube video to autoplay without the app crashing?
Many thanks.
EDIT
Totally my bad - ytObj is a strong property - I forgot to mention this. Ive added the code to reflect this.
EDIT 2
Ive added a breakpoint on the dealloc method, and this is the call stack:
0 -[MyYouTube dealloc]
1 objc-object::sidetable_release(bool)
2 -[MyAppDelegate playYouTube:]
The last entry here (playYouTube:) is the method that contains the code in my app delegate that is in the original post above. So, one more question:
What is objc-object::sidetable_release(bool), what does it do, and why is it releasing my YouTube object?
Why does the object deallocates immediately after it has been initialised?
Because nothing owns it.
You set it as the delegate to a view, but the delegate property of UIWebView is an assign property. The view doesn't take ownership of its delegate. This means that when ytObj goes out of scope (possibly before, depending on optimisation) nothing owns it, so it goes away.
EDIT
You also need to make sure that when a ytObj is deallocated, you set the delegate property of any views it is still a delegate of to nil. Otherwise the views will continue to try to send messages to the deallocated object.
How can I get the YouTube video to autoplay without the app crashing?
You need to make sure that ytObj lasts as long as the view of which it is the delegate and when it is deallocated, it's view's delegate is set to nil.
Another minor issue. Your -init function should test that self is not nil after invoking self = [super init]. It shouldn't run any of the rest of the initialisation code if self is nil.
1) (my first answer to your first question, before your edit)
This is happening because you're using ARC (automated reference counting) and you are not keeping your local "ytObj" variable ((which is NOT the "self.ytObj" property) around in the object that you created it in. As soon as the function that created the local "ytObj" variable finishes up and returns, the object is automagically dealloc'd.
Change this:
ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
to this:
self.ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
And for "best practices", I'd also suggest not doing so much work and/or code in your application delegate. App delegates are meant to receive application specific messages (like "application is suspending", "application is becoming active again", etc.), and you should do stuff like this in a subclassed view controller.
2)
The "respondsToSelector:" error you're seeing is because your YouTube object has been automagically dealloc'd. If it were still living, you wouldn't see that message.
3)
If you search here on Stackoverflow, you'll find other questions and answers that explain how to do autoplaying... like how about this one?
The object ytObj since is not strong referenced anywhere, it exists only inside the scope where is defined. Delegates most of time are declared as weak properties. None is keeping a strong reference to this object, thus ARC releases it.
Create a strong properties to ytObj and you will see everything working fine.
I am having some EXC_BAD_ACCESS problems whilst trying to stop a video that is being played through MPMoviePlayerController. Here is some code:
Video Class:
#interface MyVideo()
#property (nonatomic, strong) MPMoviePlayerController * videoController
#end
#implementation MyVideo
#synthesize videoController;
- (MyVideo*) initIntoView: (UIView*) view withContent (NSDictionary*) contentDict {
self=[super init];
NSString * rawUrl=[[NSString alloc] initWithFormat:#"http://.../%#.mp4", [contentDict objectForKey:#"filename"]];
NSURL * videoUrl=[[NSURL alloc] initWithString:rawUrl];
videoController = [[MPMoviePlayerController alloc] initWithContentURL:videoUrl];
videoController.movieSourceType=MPMovieSourceTypeFile;
videoController.view.frame = viewRef.bounds;
[videoController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
videoController.controlStyle=MPMovieControlStyleNone;
[view addSubview:videoController.view];
return self;
}
/* other code */
- (void) stop {
NSLog(#"video stop");
[videoController stop];
}
#end
This MyVideo class is a property within my AppDelegate class, like so:
#property (nonatomic, strong) MyVideo video;
A line in my AppDelegate class fires off the public method stop of this MyVideo class, like so:
[video stop];
This usually works fine. But occasionally I get an EXC_BAD_ACCESS error on the line with [videoController stop]. The line before it, the one with NSLog on it, outputs to the console as expected.
This crash happens while the video that has been loaded into the videoController is still playing. But it does not happen every time.
Can anyone suggest why this crash is happening? I suspect its because videoController is no longer in memory, despite it being strong and still in use.
Am I right in thinking there is absolutely no way of testing the videoController to see if it is still in memory?
Am I right in thinking there is absolutely no way of forcing videoController to stay in memory while it is being used to play a video?
So instead of trying to stop the video and shut down the MyVideo class properly when I dont want to play the video anymore, I am now thinking of just setting the MyVideo class to nil, and let ARC deal with stopping the video and clearing it from memory. Is this the right way of doing this? Would there be any disadvantages to this?
Are there any other solutions to this problem that I am missing?
With EXC_BAD_ACCESS my first port of call is to enable Zombie Objects in my Debug Scheme.
This should give you an idea of what object is causing the EXC_BAD_ACCESS. Just to double check it is your videoController.
When does your stop function get called on MyVideo
Is the crash on specific devices, iPad iPod, does it occur on specific os's iOS 6,7
Is it the same video file?
It cant randomly break there must be some pattern that is causing the EXC_BAD_ACCESS
I may be wrong but I have a feeling that its a threading issue. I suspect the thread calling the [myVideo stop] function is not aware of the videoController(probably initialised on the main thread). Try calling the [videoController stop] within the main thread by using the following:
dispatch_async(dispatch_get_main_queue(), ^{
[videoController stop];
});
Do let me know if this works!
Hi I am working on a iPad app and got a requirement to dismiss all popovers (if any) when app goes in background.
I did some study online and didn't find a simple way to do it. I'd like to share some my idea here and see if there are a better way to do it.
1, Dismiss popovers in didEnterBakcground in delegate. Seems not practical since we have to add all popovers reference in.
2, Go through all views recursively in current window to find popover view by (class = _UIPopoverView). It is seems a bit hacky and dangerous.
3, Set up UIApplicationDidEnterBackgroundNotificationgroundNotification in each object who own popovers and dismiss them. This seems reasonable, but really troublesome if there are hundreds of popovers in your app.
4, How about add a category method say -(void)dismissWhenAppWillEnterBackground; and register notification.
Or there is easier way to do it?
Here is a drop-in category on UIPopoverController that does what you're asking.
Basically the category swizzles initWithContentViewController: so that it can track live UIPopoverController instances in a NSHashTable (which doesn't itself hold the contained UIPopoverControllers alive since it keeps weak references to them.) It also monitors for UIApplicationDidEnterBackgroundNotification, and when this arrives it iterates the collection of live UIPopoverControllers and dismisses any that are showing.
It might be nice to extend this to implement the "never allow two popovers to show at once" rule that Apple has.
I'm not a huge fan of method swizzling in production apps but this seems pretty safe.
No special instructions for use. Just include the category in your project and use your UIPopoverControllers normally.
#import <objc/runtime.h>
#interface UIPopoverController (autodismiss)
#end
#implementation UIPopoverController (autodismiss)
static NSHashTable* ts_popoverHashTable;
+ (void) load
{
SEL originalSelector = #selector(initWithContentViewController:);
SEL replacementSelector = #selector(ts_initWithContentViewController:);
Method originalMethod = class_getInstanceMethod( [UIPopoverController class], originalSelector);
Method replacementMethod = class_getInstanceMethod( [UIPopoverController class], replacementSelector);
method_exchangeImplementations(originalMethod, replacementMethod);
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector( applicationDidEnterBackgroundNotification: )
name: UIApplicationDidEnterBackgroundNotification
object: nil];
}
- (id) ts_initWithContentViewController: (UIViewController*) contentViewController
{
UIPopoverController* pc = [self ts_initWithContentViewController: contentViewController];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ts_popoverHashTable = [NSHashTable weakObjectsHashTable];
});
[ts_popoverHashTable addObject: pc];
return pc;
}
+ (void) applicationDidEnterBackgroundNotification: (NSNotification*) n
{
for ( UIPopoverController* pc in ts_popoverHashTable )
{
if ( pc.isPopoverVisible )
{
[pc dismissPopoverAnimated: NO];
}
}
}
#end
I may have a better answer, which is add a category method -(void)dismissWhenAppWillEnterBackground to UIPopoverController and register UIApplicationWillEnterBackgroundNotificationgroundNotification.
Write a protocol with a couple of optional methods:
- (void)appWillEnterBackground;
- (void)appWillBecomeActive;
Make your view controllers to implement it and then in your app delegate, access your root view controller, check if it responds to those methods and invoke them when the app is going to background and becoming active.
You should be able to obtain the root view controller easily. If you have a hierarchy of view controllers you may need to forward the call.
Add your popover dismissal code in appWillEnterBackground, for instance.
Create a uiviewcontroller base class for all the view controllers in the application.
Add an array which contains the references to the popover views in the particular viewcontroller
Maintain a reference to the current viewcontroller iin app delegate .
When app enters in background, get the current viewcontroller and travers the popover array and dismiss all the popovers.
I've asked a question about the same issue before, and the solutions worked, but it was not a compatible iOS 4.3 solution, and I thought my design is not the right one.
Now I want to show a MFMailComposeView(Controller) as a modal view on top of my RootView(Controller) when i press a button. And instead of making it the delegate i want to make a simple NSObject which implements the protocol.
Who is also capable to show the MFMailComposeView(Controller) in the RootViewController.
I am trying this design/solution which gives me memory allocation/access problems.
RootViewController.m:
- (IBAction)tapExportButton:(id)sender
{
SendMailController *sendMailController = [[SendMailController alloc]initWithParentViewController:self];
[sendMailController openMailDialog];
[sendMailController release];
}
SendMailController.h
#interface SendMailController : NSObject <MFMailComposeViewControllerDelegate>
- (id)initWithParentViewController:(UIViewController *)parentViewController;
- (void)openMailDialog;
#property (retain, nonatomic) UIViewController* parentViewController;
#end
SendMailController.m
#import "SendMailViewController.h"
#implementation SendMailController
#synthesize parentViewController = _parentViewController;
- (id)initWithParentViewController:(UIViewController *)parentViewController
{
if (self=[super init]) {
self.parentViewController = parentViewController;
}
return self;
}
- (void) dealloc
{
self.parentViewController = nil;
[super dealloc];
}
- (void)openMailDialog
{
if ([MFMailComposeViewController canSendMail])
{
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
...
mailer.modalPresentationStyle = UIModalPresentationPageSheet;
[self.parentViewController presentModalViewController:mailer animated:YES];
[mailer release];
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
switch (result)
...
// Remove the mail view
[controller.parentViewController dismissModalViewControllerAnimated:YES];
}
#end
When I set a breakpoint in the delegation method, it crashes already before that.
Is is something with the delegate property of mailer (MFMailComposeViewController)?
The problem is that you create your instance of SendMailController and try to show the composer view, and then you release the SendMailController. This causes it to be deallocated. It looks like it works because the composer view is on screen - this is because it has been retained by the presentModalViewController call.
To fix, you need to retain your instance of SendMailController and release it when the composer has been dismissed.
The correct way to do it (and required if you use ARC, and you should use ARC) is to provide a delegate callback to tell the owner that it's finished - which kind of makes the class pointless if all it does is wrap the composer.
The cheating way (which only works when not using ARC, and which you need to be very careful with) is to have your object retain itself when it presents the composer and release itself when the composer is dismissed.
The underlying problem being your root view controller containing all the logic, you should look at using child view controllers (if a single screen holds all your UI). Usually though, your root view should be the simple class (like a master list of options) and the views it presents would be more complex (the detail views). You need to look at ensuring that the appropriate class owns the responsibility for each screen of UI.