UIScrollView UITextView and camera causing disappearing GUI bug - objective-c

I have an intermittent bug that is confounding me. Any advice on how to track it down or what might be the cause are greatly appreciated.
I have a "DetailView" with a few labels, an Image View and a Text View. In the navbar I also have a camera button to open an Image Picker and take a picture (later added to the image view). Basic stuff.
Sometimes, when taking a picture and then editing the text: the whole view between the navbar and the keyboard goes blank (to my background color). Happens more often the "first time". Repeating the procedure does not give the same problem. Happens almost only on the 3Gs (very rare on the 3G and the original iPhone).
I have two theories.
1 is that it has something to do with the scroll view that is the container for the disappearing GUI elements. The view hierarchy is: ScrollView -> UIView -> labels, texts and image. Is it a bad thing to have the scrollview as the "main" view?
2 is that it has to do with memory. The 3Gs has a better camera and takes bigger pictures... Possibly something happens if the app gets a low memory warning while taking the picture (not uncommon)?
Are any of these two at all feasible? Any other ideas on what to look for?
thanks
Update:
Could two simultaneous animations cause the bug?
On the KeyboardWillShow notification I resize the ScrollView using the UIView beginAnimations ... commitAnimations and right after that (which happens asynchronously I believe) I also tell the scroll view to scrollRectToVisible for the TextView.
like this:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
CGRect rect = [[self view] frame];
rect.size.height -= keyboardFrame.size.height * (up? 1 : -1);
[[self view] setFrame: rect];
[UIView commitAnimations];
// Scroll the active text field into view.
DetailView *tempScrollView = (DetailView *) [self view];
CGRect textFieldRect = [comments frame];
[tempScrollView scrollRectToVisible:textFieldRect animated:YES];

The 3GS also has twice the RAM of the 3G/original (256MB vs 128), so I doubt it's memory-related. You can override didReceiveMemoryWarning: to check, though.
When exactly does the view go blank? Does it happen spontaneously, or only when you press a certain button, etc. For example, does it only happen when you start editing the text view (i.e. when the keyboard pops up)? If so, check your view autoresizing masks.
Are the views actually gone, or are they just offscreen? Try printing their frames to the console or using the debugger to check where exactly they should be.
When you find the bug, please post your solution- it sounds like a problem I might encounter some day.

I managed to track it down in the Sim.
Here it goes:
Going to the DetailView
Tapping the camera buttom to "open" the UIImagePickerController (camera or Library, does not matter)
Now I simulate a memory warning before closing the Picker
Choose the image to close the Picker
Tap the UITextView
Under these circumstances the view will receive TWO UIKeyboardWillShowNotification are eachother... so my copied example code for resizing the view is run twise... making it 416px - 216px - 216px = -16px in height... not a good thing.
The reason for the double notifications were of-course that the ViewController added itself as an observer in viewDidLoad... which runs again when the view "appears" after the memory warning... but the ViewController never removed itself as an observer.
Doing that fixed the bug for sure:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
(void)viewDidUnload {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Related

Set UIWebView Content not to move when keyboard is shown

I've done large enough research but couldn't find answer to my question.
Suppose I have a webView in which I have some text fields, some of them are placed on the bottom of screen so that when keyboard appears it should hide that fields. After keyboard appears the content of webView slides up in order to make that fields visible. The problem is that I DON'T want the content to slide up.
Question is: How can I disable that feature of webview , or somehow make the content not scroll up.???
Thanks, any help would be appreciated.
If you want to disable ALL scrolling, including the auto-scroll when you navigate between form fields, setting webView.scrollView.scrollEnabled=NO doesn't quite cover everything. That stops normal tap-and-drag scrolling, but not the automatic bring-field-into-view scrolling when you navigate around a web form.
Also, watching for UIKeyboardWillShowNotification will let you prevent scrolling when the keyboard appears, but that will do nothing if the keyboard is already up from editing a different form field.
Here's how to prevent ALL scrolling in three simple steps:
1) After you create the UIWebView, disable normal scrolling:
myWebView.scrollView.scrollEnabled = NO;
2) Then register your view controller as the scrollView's delegate:
myWebView.scrollView.delegate = self;
(And make sure to add <UIScrollViewDelegate> to your class's #interface definition to prevent compiler warnings)
3) Capture and undo all scroll events:
// UIScrollViewDelegate method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
scrollView.bounds = myWebView.bounds;
}
I think this class should help with your problem. It scrolls content up, so if u have textfield at the bottom of the screen, it will move it above the keyboard.
I found the solution for me. I just scroll it down again after scrolling up.
First I catch notification UIKeyboardWillShowNotification by adding observer to it
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
then implement method:
-(void)keyboardWillShow:(NSNotification*)aNotification{
NSDictionary* info = [aNotification userInfo];
float kbHeight = [[NSNumber numberWithFloat:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height]floatValue];
float kbWidth = [[NSNumber numberWithFloat:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.width]floatValue];
BOOL keyboardIsVisible=[self UIKeyboardIsVisible];
//Check orientation and scroll down webview content by keyboard size
if(kbWidth==[UIScreen mainScreen].bounds.size.width){
if (!keyboardIsVisible) {
[[chatView scrollView] setContentOffset:CGPointMake(0, -kbHeight+10) animated:YES];
} //If is landscape content scroll up is about 113 px so need to scroll down by 113
}else if (kbHeight==[UIScreen mainScreen].bounds.size.height){
if (!keyboardIsVisible) {
[[chatView scrollView] setContentOffset:CGPointMake(0, -113) animated:YES];
}
}
}
This is not what exactly I asked but helped me to solve my problem.
Thanks.
Originally I was looking to stop my webview from zooming in when an input field (in my case the comments section) was focused on. The entire view would zoom in automatically and put everything out of whack.
I managed to do it with this, and at the same time I got the webview to stop scrolling up and out of the way of the soft keyboard as well:
//Stop the webview from zooming in when the comments section is used.
UIScrollView *scrollView = [_webView.subviews objectAtIndex:0];
scrollView.delegate = self;
//_webView.scrollView.delegate = self; //for versions newer than iOS5.

Become first responder without animation

Is there a way for a UITextField to become first responder without the animation of the keyboard? That is, make it such that the keyboard just appears?
Basically, I'm pushing a second UIViewController over the UIViewController that has the UITextField, and when that second view controller gets popped off the stack, I want the UITextField to immediately have first responder status so that when the second view controller gets popped, the user never notices the text field wasn't first responder.
Right now I have it so that when it's popped, the keyboard animates up the screen, but I don't want that to be seen.
Any ideas?
You can use a UIView animation like so
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.0];
[UIView setAnimationDelay:0.0];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[textField becomeFirstResponder]; // <---- Only edit this line
[UIView commitAnimations];
This will cause the keyboard to suddenly appear.
You can do the same but with -resignFirstResponder
Swift ..
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.0)
UIView.setAnimationDelay(0.0)
someTextView.resignFirstResponder()
UIView.commitAnimations()
Just set it as the first responder in -viewDidAppear to make the user never notice the field having lost its status.
-(void)viewWillAppear:(BOOL)animated {
if ([self isViewLoaded] && self.textField)
[self.textField becomeFirstResponder];
[super viewWillAppear:animated];
}
I've put a short sample project up on dropbox using this code, if you'd like it.
I'm, mot sure what iOS version do you use or at what point do you call becomeFirstResponder
Calling [textFieldReference becomeFirstResponder] in viewWillAppear: for iOS5/iOS6 seems to work just the way you wanted it to:
it's being called just before view controller will show it's view (that's if you don't handle view controller hierarchy manually) and as soon as it appears in navigation controller keyboard is already presented and text field has focus.
That said, there is no public way that I heard off that would allow to specify keyboard appearance style.
I don't think this was an option when the question was first asked, but UIView.performWithoutAnimation works appropriately.
UIView.performWithoutAnimation {
self.composeView.focusTextField()
}

How to handle autorotation in AVCaptureVideoPreviewLayer?

My application supports all orientations except PortraitUpsideDown.
In my view hierarchy I have an AVCaptureVideoPreviewLayer as a sublayer in the top view which is UIImageView. Then below it in view hierarchy are several overlay views showing controls.
Overlay views are working properly with orientation changes, but I don't how to be with this AVCaptureVideoPreviewLayer. I want it to behave like in Camera app, so that previewLayer stays still and controls are smoothly reorganized. Right now since the main view is rotated on orientation change, my preview layer is also rotated, which means that in landscape view it stays in portrait view, taking only part of the screen and the picture from camera being also rotated by 90 degrees. I've managed to rotate the preview layer manually, but then it has this orientation change animation, which leads to the background being seen for a while during the animation.
So what is the proper way to autorotate the controls while making previewLayer stay still?
In my implementation I have subclassed the UIView for my view which I want to rotate and something like viewController for this view which is just a subclass of NSObject.
In this viewController I do all the the checks related to changes of orientation, make decision if I should change orientation of my target view, and if yes, then I call method of my view for changing its orientation.
First of all we need to fix the orientation of whole application interface to Portrait mode, so that our ACCaptureVideoPreviewLayer always stays still.
This is done in the MainViewController.h:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation`
{
return interfaceOrientation==UIInterfaceOrientationPortrait;
}
It returns NO to all orientations except Portrait.
In order to our custom viewController be able to track the changes of device orientation we need to make it an observer of corresponding notifications:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(orientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil];
I put these lines in the (void)awakeFromNib method of my viewController.
So each time the device orientation is changed, the viewController's method orientationChanged will be called.
Its purpose is to check what is the new orientation of device, what was the last orientation of device and decide if to change it. Here is the implementation:
if(UIInterfaceOrientationPortraitUpsideDown==[UIDevice currentDevice].orientation ||
lastOrientation==(UIInterfaceOrientation)[UIDevice currentDevice].orientation)
return;
[[UIApplication sharedApplication]setStatusBarOrientation:[UIDevice currentDevice].orientation animated:NO];
lastOrientation=[UIApplication sharedApplication].statusBarOrientation;
[resultView orientationChanged];
If the orientation is the same as before or in PortraitUpsideDown then do nothing.
Else it sets the status bar orientation to the proper one, so that when there is an incoming call or ossification, it will appear on the proper side of the screen. And then I call also method in the target view where all the corresponding changes for new orientation are done, like rotating, resizing, moving the other elements of interface in this view corresponding to the new orientation.
Here is the implementation of the orientationChanged in target view:
Float32 angle=0.f;
UIInterfaceOrientation orientation=[UIApplication sharedApplication].statusBarOrientation;
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
angle=-90.f*M_PI/180.f;
break;
case UIInterfaceOrientationLandscapeRight:
angle=90.f*M_PI/180.f;
break;
default: angle=0.f;
break;
}
if(angle==0 && CGAffineTransformIsIdentity(self.transform)) return;
CGAffineTransform transform=CGAffineTransformMakeRotation(angle);
[UIView beginAnimations:#"rotateView" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:0.35f];
self.transform=transform;
[UIView commitAnimations];
Of course here you can add any other changes like translation, scaling of different views of your interface that need to respond to new orientation and animate them.
Also you may not need the viewController for this, but do all just in the class of your view.
Hope that the general idea is clear.
Also don't forget to stop getting notification for orientation changes when you don't need them like:
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
iOS 8 solution:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
} else {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
}
}
in your setup code:
self.layer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
self.layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
} else {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
}
THe main problem is that when I get the notification, the statusbar has not yet rotated, so checking the current value give in fact the value before the rotation. So I added a little delay (here 2 seconds) before calling the method that check the statusbarorientation and rotates my subview :
-(void) handleNotification:(NSNotification *) notification
{
(void) [NSTimer scheduledTimerWithTimeInterval:(2.0)
target:self
selector:#selector(orientationChanged)
userInfo:nil
repeats:NO ] ;
}
The rest of the code is the one from BartoNaz.

How to switch to Landscape View properly?

I have a table view with some words, and i present flash-card style landscape view when the device rotates. I made it by observing the "UIDeviceOrientationDidChangeNotification".
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(openLandscapeMode) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
1)That works fine and smooth, but the problem is that when the we are in the landscape, i don't want the viewcontroller to react to the spinning around the vertical axis,so that i could lay the phone on the table and it would still be in the landscape.
Maybe i should somehow observe the horizontall spinnings, instead of deviceorientation?
-(void)openLandscapeMode
{
if([[UIDevice currentDevice]orientation]==UIDeviceOrientationLandscapeLeft||[[UIDevice currentDevice]orientation]==UIDeviceOrientationLandscapeRight)
{
LandscapeCardViewController *landscape = [[LandscapeCardViewController alloc]init];
landscape.words = words;
landscape.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:landscape animated:YES];
NSLog(#"Switch to %#",[[NSUserDefaults standardUserDefaults]valueForKey:#"ChosenWordInCard"]);
[landscape release];
}
else
{
[self dismissModalViewControllerAnimated:YES];
[[UIApplication sharedApplication]setStatusBarOrientation:UIInterfaceOrientationPortrait];
}
}
2)The second question is where to remove observer, if this controller is in a tab bar, and i want to perform the same transition in another controller in the same tabbar,but,of course,with another landscape view?
I tried in viewWillDissappear, but it doesn't work properly.
Thanks a lot!
For your first question, there should be a method in your viewcontroller which you may need to edit to only support portrait
-(BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation {
return UIInterfaceOrientationPortrait(interfaceOrientation); //only allow portrait
}
That will stop it auto rotating to landscape, while keeping your original method intact
For the second. What about when the transition is complete? Then re-add it when the view appears again. And then in your landscape controller, add it to re-detect when the device is portrait.
I found the solution
I changed else to if([[UIDevice currentDevice]orientation]==UIDeviceOrientationPortrait||[[UIDevice currentDevice]orientation]==UIDeviceOrientationPortraitUpsideDown) and everything works fine!
Strange, but it works!
About removing the observer - i do it in -viewWillAppear,checking,if i am not in landscape now.

iPad Orientation issues when switching between two views and from portrait to landscape vice versa

I am building a simple app with two View Controllers, I am testing the code using the iPhone Simulator, everything seem to be working fine. The problem happens when I rotate from Portrait to Landscape or from Landscape to portrait. This is the logic of the app, the app always launched in Portrait, I have a button to on the first View to Switch from View1 to View2. On View2 I have another button to switch from View2 back to View1. Let say, I am in Portrait mode, I switch from View1 to View2, then rotate the iPad (in the simulator) from Portrait to Landscape, when I switch back from View2, i.e to go back to View1. View1 screen/view is displayed in Portrait with View2 screen displayed in the background, ie part of View2 is displayed in the background, I guess because View1 was originally in Portrait mode.
The question is.. Has anyone had this issue before, if so, any code to fix this issue, secondly, how can I identify in the code which orientation the device is and which orientation the view is in.
This method is to switch to View 2:
-(IBAction) switchToView2: (id) sender {
SecondViewController *myViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
[self.view addSubview: myViewController.view];
[UIView commitAnimations];
}
This method is to switch back to View1:
-(IBAction) switchBackToView1:(id) sender {
[self.view removeFromSuperview];
[UIView commitAnimations];
}
From your code:
[self.view addSubview: myViewController.view];
It makes me believe that your 2 views is a subview of myViewController.view. which explains why they're both showing at the same time. it would make sense to have seperate view controllers for different views.
First i think
[UIView commitAnimations];
is not necessary :
From Apple
commitAnimations
Marks the end of a begin/commit animation block and schedules the animations for execution.
second in Interface Builder have you set the property of your controller to landscape ?
Hope this Help (sorry for my bad English)
Yes, that would happen if you dont put any orientation change handling in your code. Check out this guide on how you can set your view to automatically or manually handle adjustment on orientation change.
Or if you do have handling already, then it may be because of how you are adding/removing your views. For better handling, I think you should try the UINavigationController way of managing views. i.e., instead of addSubview: and removeFromSuperview:, you should use pushViewController:animated: and popViewController:animated: instead.
And yes, as Yoos said, [UIView commitAnimations] is not needed in your code above.
Hope this helps.