I have an Objective-C app with a programmatically created view for which I'm seeing strange (to me) behavior. Specifically, there's a long pause (up to 30 seconds) between the end of the init method and the beginning of the loadView method. I'm unable to see anything out of the ordinary when setting breakpoints at both those places, so I'm wondering what the system is doing between the init and the loadView, and whether there's anything I can do to optimize the work being done at that point.
Thanks.
Edit: adding some code below (abbreviated for clarity)
The app plays and records video. This view controller reviews a video that's just been recorded but not yet saved / posted.
// called from the view controller that pushes this onto the navigation controller stack
- (id)initWithURL:(NSURL *)fileURL
{
self = [super init];
if (self) {
self.localFileURL = fileURL;
sessionManager = [[sessionManager alloc] initWithURL:fileURL];
}
NSLog(#"return self");
return self;
}
- (void)loadView
{
NSLog(#"begin loadView");
self.playView = [[PlayView alloc] initWithFrame:screenRect];
self.view = self.playView;
}
The pause occurs between the two logging statements.
Related
I ran this code here, and it worked perfectly.
However, I’m facing a problem when trying to add it to my project.
After the class YouTubeHelper call showAuthenticationViewController:
– (void)showAuthenticationViewController:(UIViewController *)authView {
[self.navigationController presentViewController:authView animated:NO completion:nil];
}
Inside GTMOAuth2ViewControllerTouch.m I’m getting an exc_bad_access, inside loadView, on [super loadView], as below:
– (void)loadView {
NSString *nibPath = nil;
NSBundle *nibBundle = [self nibBundle];
if (nibBundle == nil) {
nibBundle = [NSBundle mainBundle];
}
NSString *nibName = self.nibName;
if (nibName != nil) {
nibPath = [nibBundle pathForResource:nibName ofType:#”nib”];
}
if (nibPath != nil && [[NSFileManager defaultManager] fileExistsAtPath:nibPath]) {
[super loadView]; <<<< exc_bad_access here!
} else {
// One of the requirements of loadView is that a valid view object is set to
// self.view upon completion. Otherwise, subclasses that attempt to
// access self.view after calling [super loadView] will enter an infinite
// loop due to the fact that UIViewController's -view accessor calls
// loadView when self.view is nil.
self.view = [[[UIView alloc] init] autorelease];
#if DEBUG
NSLog(#"missing %#.nib", nibName);
#endif
}
}
Any idea why this is happening and how to fix it?
Thanks!
You should never call [super loadView]; in your subclass. Remove that line from your implementation.
You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view
hierarchy to the view property. The views you create should be unique
instances and should not be shared with any other view controller
object. Your custom implementation of this method should not call
super.
Reference: UIViewController Class Reference
Thanks Midhun and EridB.
As the libraries are a little bit old, I did my own implementation to upload videos to YouTube.
Here some links that helped on it:
https://developers.google.com/youtube/v3/guides/implementation/videos
https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
Specially this one was very useful to try and check request/response:
https://developers.google.com/oauthplayground/?code=4/1c_ILMzPMhgPWpCI6L7M-LeBe5jL-BY1Xa1sS0oWQJ0
I hope it helps others with YouTube API implementation.
I've read a bunch of answers on SO already, but I'm a little confused.
I have a tab bar controller subclass I created, and in its viewDidLoad, I'm creating each of the view controllers. However, I have a dependency that I'm passing into the parent, and in turn into the view controller for each tab. I'm passing that dependency in with a custom init method (NS_DESIGNATED_INITIALIZER declared for it in the header). However, it looks like [super init] triggers viewDidLoad directly, so the dependency isn't set properly when the other view controllers are created.
Here's my custom init method:
- (instancetype)initWithSession:(T2Session *)session
{
self = [super init];
if (self) {
_session = session;
}
return self;
}
I'd like session to be set by the time I create the view controllers, but I'm sort of confused about what the best way to do this is. Any advice is much appreciated. Thanks in advance!
I've come across this situation before.
You probably sat there (like me) wishing viewDidLoad didn't get called so soon.
Anyway, this is what I settled on:
- (instancetype)initWithSession:(T2Session *)session {
if (self = [super init]) {
self.session = session;
}
return self;
}
- (void)setSession:(T2Session *)session {
_session = session;
... call the setup methods here, instead of viewDidLoad
}
At first I thought this breaks the golden rule of not calling self.xxxx from within an initializer.
However, I think that rule is only really relevant when calling methods on IBOutlets that may not have been wired up yet.
In this case, T2Session *session isn't a nib outlet.
Alternatively, if you prefer not to break that rule you could always remove the custom initializer .. and revert to using regular property-injection rather than constructor-injection. e.g.
T2Session *session = .....
MYTabBarController *tabBarController = [[MYTabBarController alloc] init];
[tabBarController setSession:session];
These are just my thoughts, hope it helps.
I am using XCtest to test the title of a view. Trying to get into the habit of writing tests first. Setup looks like
- (void)setUp
{
[super setUp];
self.appDelegate = [[UIApplication sharedApplication] delegate];
self.tipViewController = self.appDelegate.tipViewController;
self.tipView = self.tipViewController.view;
self.settingsViewController = self.appDelegate.settingsViewController;
self.settingsView = self.settingsViewController.view;
}
The problem is "settingsViewController". I have two functions for the actual test:
- (void) testTitleOfMainView{
XCTAssertTrue([self.tipViewController.title isEqualToString:#"Tip Calculator"], #"The title should be Tip Calculator");
//why does this not work?
// XCTAssertEqual(self.tipViewController.title, #"Tip Calculator", #"The title should be Tip Calculator");
}
- (void) testTitleOfSettingsView{
//make the setttings view visible
[self.tipViewController onSettingsButton];
//test the title
XCTAssertTrue([self.settingsViewController.title isEqualToString:#"Settings"], #"The title should be Settings");
}
The "testTitleOfMainView" works. But the "testTitleOfSettingsView fails as self.settingsViewController is nil. I can sort of understand why. The view has not been initialized as yet. So I tried sending the message to the main controller that brings the settignscontroller in view
[self.tipViewController onSettingsButton];
The settingsController is still nil. Should I be using mocks? Somebody suggested this for my other question
xctest - how to test if a new view loads on a button press
Should I subclass the settingsview and bring it up manually? Thank you.
Stay away from actually loading views in a real navigation stack. Real UI interactions typically need the run loop to receive events, so they will not work in a fast unit test. So throw away your setUp code.
Instead, instantiate the view controller on its own, and have it load:
- (void)testTitleOfSettingsView
{
SettingsViewController *sut = [[SettingsViewController alloc] init];
[sut view]; // Accessing the view causes it to load
XCTAssertEquals(#"Settings", sut.title);
}
Also, learn the various assertions that are available to you in XCTest, not just XCAssertTrue. Avoid comments in those assertions; a single assertion in a small test should speak for itself.
I am trying to use the iOS zxing Widget for QR Code Scanning. I have a ViewController which is pushed as an Item in my UINavigationController or presented Modally from another ViewController. This ViewController has a SegmentedControl for 3 different views. Two of those Views are UIWebViews which load simple Websites, nothing special about them.
The selection looks something like this:
- (IBAction)segmentedControlValueChanged:(id)sender {
NSString *urlString;
ZXingWidgetController *widController;
QRCodeReader* qrcodeReader;
NSSet *readers;
switch (segmentedControl.selectedSegmentIndex) {
case 0:
[self.view bringSubviewToFront:self.productSearchWebView];
urlString = [[SACommunicationService sharedCommunicationService] getURLforKey:kURLTypeProductSearch];
[self.productSearchWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
break;
case 1:
[self.view bringSubviewToFront:self.marketSearchWebView];
urlString = [[SACommunicationService sharedCommunicationService] getURLforKey:kURLTypeMarketSearch];
[self.marketSearchWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
break;
case 2:
widController = [[ZXingWidgetController alloc] initWithDelegate:self showCancel:YES OneDMode:NO];
qrcodeReader = [[QRCodeReader alloc] init];
readers = [[NSSet alloc] initWithObjects:qrcodeReader,nil];
widController.readers = readers;
[self.QRCodeScannerView addSubview:widController.view];
[self.view bringSubviewToFront:self.QRCodeScannerView];
break;
default:
break;
}
}
I tried to debug and go step by step and find out where the problem comes from:
Decoder (which is part of the underlying ZXing logic) tries to call "failedToDecodeImage:" from its delegate (which should be the ZXingWidgetController class) and crashes (EXC_BAD_ACCESS)
While stepping through I found out that the "cancelled" Method of the ZXingWidgetController gets called. Now I don't really know why this method gets called. The Widget shouldn't stop right after initializing and starting the decoder.
So the answer is a very simple one.
I was using iOS 5.0 and ARC. The ZXing ViewController is instantiated locally inside the method. Since the ViewController itself does not get viewed ARC sets a release at the end of the method and the ViewController gets freed. Since the ViewController is released, the view which was retained by the ViewController will be released as well. Cancelled is called because the Main ViewController does not exist anymore and calling some method on a nil pointer results in a BAD_ACCESS.
The Solution here was to set the ZXingViewController as a global strong property. This kept the object from being released right at the end of that method and thus the view which was added as a subview to another ViewControllers view was held in memory as long as the ViewController was alive.
You're not supposed to add controller views as subviews of another view. You're suposed to present controllers using the various UIViewController mechanisms.
By doing what you're doing, you're violating UIViewController contracts. The widget isn't getting things like viewWillAppear, viewDidAppear, etc.
If you want to use ZXing at the UIView/CALayer level instead of the UIViewController level, look at the classes in the ZXing objc directory.
Try this... also in the .h file make this ZXingWidgetController *widController;
And also to the viewScanner set clipToBounds to true.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(openScanner) withObject:nil afterDelay:0.5];
}
-(void)openScanner
{
self.widController = [[ZXingWidgetController alloc] initMiniWithDelegate:self showCancel:NO OneDMode:YES];
NSMutableSet *readers = [[NSMutableSet alloc ] init];
MultiFormatReader* reader = [[MultiFormatReader alloc] init];
[readers addObject:reader];
self.widController.readers = readers;
[viewScanner addSubview:self.widController.view];
}
Very interesting problem when using loadView in UIViewController.
Usually we used like this way
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
NSLog(#"loadview");
[super loadView];
}
If remove
[super loadView];
We will get dead loop with this
- (void)loadView {
NSLog(#"loadview");
}
Why ?
Only one way to make infinite loop in this case - is getting view property until its not set. If you write next (for example):
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:self.view.bounds];
}
You'll got infinite loop, but
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectZero];
}
works OK.
So you can't to access view property until you didn't set it.
Since you are just INHERITING what's being implemented in super class(UIViewController), if you don't call super methods, then implementation that needs to be done is NOT done.
Almost all super methods do something critical and the local class inheriting super class implementations must either override them all together (unless you know everything about what super does by referring to the documentation, it's never a good idea), or just ADD local class implementation to the inherited super class implementations.
In conclusion, whenever you inherit a class, which is in most cases of software development, you should let the super class do its implementations unless it's safe to override them.
If I am correct, it seems like super loadView implements something very critical to avoid the loop.
ADDITIONAL NOTE:
However, based on the documentation, you should not call super method: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
Probably, the reason for infinite loop is caused by not implementing view property appropriately.
If you override loadView, you are expected to provide a root view for the controller's view hierarchy. If you don't provide it loadView would get called every time the view is referenced, possibly leading to an infinite loop. From the docs:
If you specify the views manually, you must implement the loadView method and use it to assign a root view object to the view property.
Implementations that would cause an infinite loop:
- (void)loadView {
NSLog(#"loadview");
}
...self.view is nil after loadView
- (void)loadView {
self.view; // Or anything that references self.view
}
...referencing self.view causes loadView to be invoked, hence an infinite loop.
Correct:
- (void)loadView {
self.view = [[UIView alloc] init];
if (self.view == nil) {
[super loadView]; // Try to load from NIB and fail properly, also avoiding inf. loop.
}
}