I have a piece of code that runs fine on iOS7, but has issues in iOS8.
It is a popup, that is initialized from a nib and immediately after, it's show method is called.
The problem is, that the initFromNib in iOS8 returns immediately, before calling viewDidLoad.
Consequently all IBOutlets, that the code relies on in show are nil.
The same code is used at multiple occasions, it works with a datepicker embedded in the popup, but it seems that the tableview, that is embedded in the popup in this special case is not initializing immediately on initFromNib.
To repeat the question: Is this a new behavior in iOS8 and
How should I handle this case in objC:
Init a popup from nib and show it immediately after initialization has been finished.
Currently, I would implement my show method as a delegate that is called at the end of viewDidLoad, but this would integrate too much external workflow into the popup for my opinion. But then, I am not really experienced in iOS development ...
Thanks for helping out or pointing me to matching resources.
UPDATE: This is the code calling the view
- (void)onButtonTapped:(id)sender {
if(shouldResignFirstResponder){
[[[UIApplication sharedApplication] keyWindow] endEditing:YES];
}
isFirstResponder = YES;
if (self.beginEditingBlock!=nil){
self.beginEditingBlock([self fieldId]);
}
JXCheckListPopoverController *popover = [[JXCheckListPopoverController alloc]
initWithNibName:#"JXCheckListPopoverController" bundle:nil];
if([self getEditableFieldType] == EditableField_multipleStrings) {
valuesBeforeEditing = [self.fieldValues copy];
[popover setupPopoverWithParams:params delegate:self
fieldValues:self.fieldValues readOnly:readOnly];
} else {
NSMutableDictionary *selectedValue = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:fieldValue, #"selectedValue", nil];
self.fieldValueBeforeEditing = fieldValue ? fieldValue : #"";
[popover setupPopoverWithParams:params
delegate:self fieldValues:selectedValue readOnly:readOnly];
}
[popover show:self.bounds parent:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"fieldSelected" object:self];
self.startDate = [NSDate date];
}
viewDidLoad is called after this method has finished. I understand, that this is somewhat uncool, but I inherited this code as a bad heritage and would like to understand how to rework the workflow. As there may be hundreds of popover views present during editing (present but not visible), I cannot create them all upfront - at least not as far as as I understand the code.
This is the show method:
- (void)show:(CGRect)rect parent:(UIView *)parent {
if ([self.popoverController isPopoverVisible]){
[self.popoverController dismissPopoverAnimated:YES];
}
UIPopoverController *popover = [[UIPopoverController alloc]initWithContentViewController:self];
popover.delegate = self;
const CGFloat height = contentTableView.rowHeight* possibleValues.count;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenHeight = screenRect.size.height;
//todo: replace hardcoded sizes here
[popover setPopoverContentSize:CGSizeMake(350.0, MIN(height,screenHeight))];
self.popoverController = popover;
if (parent.window!=nil){
[self.popoverController presentPopoverFromRect:rect inView:parent permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIF_popupShow object:nil];
}
The problem is that you were always doing this wrong. iOS 8 has merely exposed the problem. Keep in mind what viewDidLoad means. It means that this view has loaded and that any outlets are now connected. Nothing more. It exists, but that is all. In particular, it is not in the interface yet. So what you were doing in viewDidLoad, you were doing too soon. Use viewDidAppear: or viewDidLayoutSubviews, for example, to know when the view is actually in the interface and we are ready to do something else.
Behavior for popovers has changed in iOS8. I'm not sure about regular view lifecycle thought. One would think that viewDidLoad will be called in a sync manner when you instantiate view from a XIB file, but judging by your comments it's not the case.
If I were on your place - I would try to create a sample small project where you can pin-point the problem and solution exactly. I know that re-factoring large body of code might not be an easy solution, but it might be necessary. In my projects I'm trying to wrap application logic for presenting alerts and popovers in a way so all differences between OS versions and devices are hidden from the application code and can adjusted easily.
I have found a solution to the problem.
I have changed the code to set the height of the tableview contained in the popup in the viewWillAppear method:
const CGFloat height = contentTableView.rowHeight* possibleValues.count;
CGFloat screenHeight = screenRect.size.height;
[popover setPopoverContentSize:CGSizeMake(350.0, MIN(height,screenHeight))];
This did not hurt the logic and it solved the problem.
The answer to the question is possibly that some change was there in nib loading, but I could not find a reference anywhere.
So I accept my answer as correct, as it solves my problem, but I feel kind of bad about it, because it does not explain the real issue. I have given the other persons answering a "+1" on their answers.
Related
I have an EAGLView-based class that runs the following code when a menu selection is made in OpenGL:
-(void) startPicker
{
self.gameState = kStatePicker;
GKPeerPickerController *picker = [[GKPeerPickerController alloc] init];
picker.delegate = self;
picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;
[picker show];
}
For some unknown reason, the picker is a blank rounded rect when it appears, with no visible interface or directions.
If I launch it at the end of my init function, it works. Afterward, it launches fine from the menu.
I have tried placing the code in startPicker inside a main queue dispatch, but that doesn't seem to help. I've tried running the picker with and without ARC, but that makes no difference. This code is more or less directly taken from the GKTank example that Apple provided a while ago, to introduce GameKit's bluetooth framework.
Can anyone tell me why this might be happening, and what a possible solution is?
Just following up. I discovered in the view controller that contains this view that there was a call to [UIView setAnimations:NO]; This prohibits GKPeerPickerController from appearing properly.
I have a UIActivity subclass that creates its own activityViewController:
- (UIViewController *)activityViewController {
WSLInProgressViewController* progressView = [[[WSLInProgressViewController alloc] init] autorelease];
progressView.message = [NSString stringWithFormat:NSLocalizedString(#"Posting to %#...",#"Posting to..."),
self.activityType];
return progressView;
}
I've add a full repro on GitHub.
According to the documentation, you aren't supposed to dismiss this manually. Instead, the OS does that when you call activityDidFinish:. This works fine when ran on an iPhone.
When I say "works," this is the sequence of events that I'm expecting (and see on the iPhone):
Display the UIActivityViewController
User presses my custom activity
My view controller appears
I call activityDidFinish:
My custom view controller is dismissed
The UIActivityViewController is also dismissed
However, when I run this same code on the iPad Simulator -- the only difference being that I put the UIActivityViewController in a popup, as the documentation says you should -- the activityViewController never dismisses.
As I say, this is code wo/the popUP works on the iPhone and I have stepped through the code so I know that activityDidFinish: is getting called.
I found this Radar talking about the same problem in iOS6 beta 3, but it seems such fundamental functionality that I suspect a bug in my code rather than OS (also note that it works correctly with the Twitter and Facebook functionality!).
Am I missing something? Do I need to do something special in the activityViewController when it's run in a UIPopoverViewController? Is the "flow" supposed to be different on the iPad?
The automatic dismissal only appears to happen when your 'activity' controller is directly presented, not wrapped in anything. So just before showing the popup it's wrapped in, add a completion handler
activity.completionHandler = ^(NSString *activityType, BOOL completed){
[self.popup dismissPopoverAnimated:YES];
};
and you'll be good.
I see the question is quite old, but we've been debugging the same view-controller-not-dismissing issue here and I hope my answer will provide some additional details and a better solution than calling up -dismissPopoverAnimated: manually.
The documentation on the UIActivity is quite sparse and while it hints on the way an implementation should be structured, the question shows it's not so obvious as it could be.
The first thing you should notice is the documentation states you should not be dismissing the view controller manually in anyway. This actually holds true.
What the documentation doesn't say, and what comes as an observable thing when you come across debugging the non-dissmissing-view-controller issue, is iOS will call your -activityViewController method when it needs a reference to the subject view controller. As it turns out, probably only on iPad, iOS doesn't actually store the returned view controller instance anywhere in it's structures and then, when it wants to dismiss the view controller, it merely asks your -activityViewController for the object and then dismisses it. The view controller instantiated in the first call to the method (when it was shown) is thus never dismissed. Ouch. This is the cause of the issue.
How do we properly fix this?
Skimming the UIActivity docs further one may stumble accross the -prepareWithActivityItems: method. The particular hint lies along the following text:
If the implementation of your service requires displaying additional UI to the user, you can use this method to prepare your view controller object and make it available from the activityViewController method.
So, the idea is to instantiate your view controller in the -prepareWithActivityItems: method and tackle it into an instance variable. Then merely return the same instance from your -activityViewController method.
Given this, the view controller will be properly hidden after you call the -activityDidFinish: method w/o any further manual intervention.
Bingo.
NB! Digging this a bit further, the -prepareWithActivityItems: should not instantiate a new view controller each time it's called. If you have previously created one, you should merely re-use it. In our case it happily crashed if we didn't.
I hope this helps someone. :)
I had the same problem. It solved for me saving activityViewController as member and return stored controller. Activity return new object and dismiss invoked on new one.
- (UIViewController *)activityViewController {
if (!self.detaisController) {
// create detailsController
}
return self.detailsController;
}
I pass through the UIActivity to another view then call the following...
[myActivity activityDidFinish:YES];
This works on my device as well as in the simulator. Make sure you're not overriding the activityDidFinish method in your UIActivity .m file as I was doing previously. You can see the code i'm using here.
a workaround is to ask the calling ViewController to perform segue to your destination ViewController via - (void)performActivity although Apple does not recommend to do so.
For example:
- (void)performActivity
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
[self.delegate performSomething]; // (delegate is the calling VC)
[self activityDidFinish: YES];
}
}
- (UIViewController *)activityViewController
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIViewController* vc=XXX;
return vc;
}
else
{
return nil;
}
}
Do you use storyboards? Maybe in your iPad storyboard, the UIActivityIndicatorView doesn't have a check on "Hides When Stopped"?
Hope it helps!
So I had the same problem, I had a custom UIActivity with a custom activityViewController and when it was presented modally it would not dismiss not matter what I tried. The work around I choose to go with so that the experience remained the same to the user was to still use a custom UIActivity but give that activity a delegate. So in my UIActiviy subclass I have the following:
- (void)performActivity
{
if ([self.delegate respondsToSelector:#selector(showViewController)]) {
[self.delegate showViewController];
}
[self activityDidFinish:YES];
}
- (UIViewController *)activityViewController
{
return nil;
}
Then I make the view controller that shows the UIActivityViewController the delegate and it shows the view controller that you would otherwise show in activityViewController in the delegate method.
what about releasing at the end? Using non-arc project!
[progressView release];
Many Users have the same problem as u do! Another solution is:
UIActivityIndicatorView *progress= [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(125, 50, 30, 30)];
progress.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
[alert addSubview:progress];
[progress startAnimating];
If you are using storyboard be sure that when u click on the activityind. "Hides When Stopped" is clicked!
Hope that helped...
I have a UIViewController called DebugViewController that contains a UITextView, and a public method called debugPrint which is used to write an NSString into the UITextView and display it.
Is it possible to write into the UITextView before I open the UIViewController, so that when I open it, the text previously written into it is displayed?
In my parent view controllers viewDidLoad method, I'm calling initWithNibName on the DebugViewController as follows
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
I then call debugPrint as follows
[debugViewController debugPrint:#"viewDidLoad"];
And some time later I call the following to open the debugViewController
debugViewController.delegate = self;
debugViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:debugViewController animated:YES];
However all the text previously written is missing.
Please let me know how I can use a view controllers methods before the view controller displayed to the user.
Thanks,
JustinP
What you are doing is a little non-standard. The danger with that as always is that if you don't really have an expert grasp on what you're doing, you can quickly find yourself in difficulty.
If you want something set before the view is displayed to the user, then the best way to do that is to do it in the viewWillAppear method. Put it there rather than in viewDidLoad because a view might loaded once but appear many times. Where you place it depends on whether the data changes from appearance to appearance.
So, if your data is pretty static and won't change, use the viewDidLoad method.
Assuming that you'll go for the viewWillAppear option, let's do the first step by having an ivar in the view controller:
NSString *myText;
set that after init:
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
debugViewController.myText = #"My text here";
then, in debugViewController's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
myTextView.text = myText;
}
The view controller life cycle is complex as you can see from the View Controller Programming Guide for iOS. So I'd say best not stray from the path of least resistance unless you have good reason. That said sometimes the best way to learn is by experimentation.
As mentioned before - I'm an Objective-C newbie of the first order but having read 4 physical books on the subject and a bucket-load of ebooks and documentation I still can't find what I'm looking for.
I have a top-level content view controller that wants to configure its view property from the physical dimensions of the window property of the application delegate. This is something that several people have already asked questions on. ([UIScreen mainScreen] doesn't cut it for reasons already aired many times before on this forum). Therefore, the logical approach would be for the content view controller to read the frame of the application delegate's window. Now, the only answer I've found that comes close to this is to use [[[UIApplication sharedApplication] window] frame] - however, this only works once the window property has been made keyAndVisible. The content view controller needs to read the app delegate's window property before it gets to makeKeyAndVisible. The code goes in this order....
App Delegate:
- (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *) launchOptions {
// This next line is a test window frame for R&D purposes....
[self setWindow: [[UIWindow alloc] initWithFrame: CGRectMake(0.0f, 20.0f, 320.0f, 320.0f)]];
if ([self window]) {
contentViewController = [[ContentViewControl alloc] initWithNibName: nil bundle: nil]; // Content view controller instantiated here
if (contentViewController) {
[[self window] addSubview: [contentViewController view]];
[[self window] layoutSubviews];
[[self window] makeKeyAndVisible]; // Window made key and visible here
return YES;
}
}
return NO;
}
In my content view controller's initWithNibName: nil bundle: nil method I have the following test code...
- (id) initWithNibName: (NSString *) nibNameOrNil bundle: (NSBundle *) nibBundleOrNil {
self = [super initWithNibName: nibNameOrNil bundle: nibBundleOrNil];
if (self) {
NSLog(#"%#", NSStringFromCGRect([[[UIApplication sharedApplication] keyWindow] frame]));
// This does not work.
}
return self;
}
This does not work due to the fact that App Delegate's window is not yet key and visible.
So, my question is thus; What is the name of my App Delegate Class' instance? I know the App Delegate's Class name defaults to myApplicationNameAppDelegate but I'm after the instance name. I want to replace the call to [[UIApplication sharedApplication] keyWindow] by something like;
[myAppDelegatesInstanceName window].
Expanding this a little, how does one access methods in other target objects that are not scope descendants of the object doing the querying?
Like I said, I'm a total noob to all of this and this is probably another dumb noobie question but it's one that nobody yet appears to have answered in a simple way.
(Procedurally - my home turf - there are a plethora of ways to get the window stuff down into other levels of scope ranging from making the window globally accessible to the whole program suite to passing it down as a specific parameter through the various function hierarchies - but this Objective stuff seems to depart from established, procedural practice).
If anyone can help, I'd be really grateful. This stuff is definitely not intuitive!
V.V.
You can access the app delegate through the singleton UIApplication instance:
[[[UIApplication sharedApplication] delegate] window];
This is a special case, though, because you can access an object (the UIApplication instance) whose delegate you know is the object you want to access. The general case:
Expanding this a little, how does one access methods in other target objects that are not scope descendants of the object doing the querying?
You need to pass a reference to the target object to the object from which you want to access it. This is sometimes necessary but it also means that you introduce a tight coupling between these two objects, which you generally want to avoid if you can. So think about each case and see if there are other possible designs.
Swift 3
if let window = NSApplication.shared().windows.first {
// access window. properties now
}
Use this for Swift:
let window:UIWindow? = (UIApplication.sharedApplication().delegate?.window)!
Every time I turn the page in my app, I am removing and releasing the previous viewController - but for some reason it is still in memory. I know this, because after using the app for a while, I get 47 memory warnings - one from each view controller - if I had opened 47 pages before the memory warning occurred. I get 60 memory warnings if I had opened 60 pages before the memory warning occurred. And so on...
This is the code that runs from page to page:
UIViewController *nextController;
Class nextClass = [pageClasses objectAtIndex:(currentPageIndex - 1)];
nextController = [[nextClass alloc] initWithNibName:[pageNibs objectAtIndex:(currentPageIndex - 1)] bundle:nil];
[nextController performSelector:#selector(setDelegate:) withObject:self];
[currentPageController.view removeFromSuperview];
[self.view addSubview:nextController.view];
[currentPageController release];
currentPageController = nextController;
[currentPageController retain];
[nextController release];
Can anybody point to any issues they see?
Thanks so much!
As as aside, make sure you also nil any outlets your viewController has in viewDidUnload and generally do the opposite of any corresponding code in viewDidLoad. I see a lot of iOS code which doesn't do this and it stops the runtime properly unloading view controllers and associated views.
Have you played with the Behavior and Memory sections of the Window Attributes panel in IB's Inspector? This is where you usually control memory use and release stuff (outside of the code itself).
Try changing values for the view object in question, as well as on the window (or whatever it is for iPhone).
I believe it's because you're calling retain on currentPageController. I just recently asked a similar question and got a ton of clarification on memory management.
EDIT: What if you did something like:
[currentPageController.view removeFromSuperview];
[currentPageController release];
Class nextClass = [pageClasses objectAtIndex:(currentPageIndex - 1)];
currentPageController = [[nextClass alloc] initWithNibName:[pageNibs objectAtIndex:(currentPageIndex - 1)] bundle:nil];
[currentPageController performSelector:#selector(setDelegate:) withObject:self];
[self.view addSubview:currentPageController.view];
It cleans up the code a bit and won't leak memory.