Funny UIScrollView when using presentViewController - ios

Summary:
I have a UIScrollView with zoomable content. If the content is NOT zoomed and I presentViewController, everything is fine when I dismiss that ViewController. But, if I have zoomed the content and then presentViewController, the content and the UIScrollView are all wacky when I dismiss the ViewController. Any help is welcome, this is an awful bug... Thank you!
Test Project:
A simple test can be found here...
http://twostatesaway.com/ModalWithScrollViewTEST.zip
or here
https://drive.google.com/file/d/0B0pG5vRVzBTzdkVzdEtkdmVjdDA/edit?usp=sharing
Screen Shots:
Screen 1: Everything fine. I can show the modal a million times and the content will work as expected after dismissing the modal
Screen 2: Zoom in on content.
Screen 3: When I click the button to presentViewController, the content shifts to the right as the ViewController (in black) is coming up.
Screen 4: Modal is on screen.
Screen 5: When I dismiss the ViewConroller, the content is funny, the bright green view seems to go back to normal, but the button stays in its zoom position, still shifted to the right.
Screen 6: Another funny things is that now I can zoom out more than before. The min zoom is set at 1.0, but now it seems like that is not working.

Try this way...
1) Remove the code from - (IBAction)openModal:(id)sender & make it blank.
2) In story board make the secondViewController class as ModalViewController as below.
3) Drag the line from button to second view & use segue as modal as shown below.
Updated answer
1) clear the viewDidLoad method.
2) Write following code in ViewWillAppear method.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.scrollView.delegate = self;
self.scrollView.minimumZoomScale = 1.0;
self.scrollView.maximumZoomScale = 5.0;
//self.scrollView.contentSize=CGSizeMake(320, 200);
[self.scrollView setZoomScale:self.scrollView.minimumZoomScale];
}

The accepted work around here is to capture the zoom and contentOffset when the modal is coming up, and then set the properties when view is appearing again...
//// Capture values
-(void)viewWillDisappear:(BOOL)animated
{
zoomedScale = self.scrollView.zoomScale;
contentOffsetTemp = self.scrollView.contentOffset;
}
//// This prevents the content from getting messed up (thanks user1673099)
-(void)viewDidDisappear:(BOOL)animated
{
[self.scrollView setZoomScale:self.scrollView.minimumZoomScale];
}
//// Then reassign them values
-(void)viewWillAppear:(BOOL)animated
{
self.scrollView.zoomScale = zoomedScale;
self.scrollView.contentOffset = contentOffsetTemp;
}

Related

Blank screen before and after the UIWebview load and next view load iOS Objective C

I have two view controllers. In first view controller, I click a button and a webview loads. Before loading web view, it shows a black screen and it goes immediately and the webviews load. Again, when I go from my webview to another view controller, I see the same black screen comes and goes immediately.
PS : This code is not written by me, and it's a little messy so I am not able to paste code here.
Please let me know if you can think of any proper reasoning why its happening and how can I resolve it?
Is there any way i can check for number of view controllers in the stack? Because sometimes instead of black screen it shows some previously loaded screen
This is the code used to remove all the view controller from stack -
UIViewController *vc = self.presentingViewController;
while (vc.presentingViewController) {
vc = vc.presentingViewController;
}
[vc dismissViewControllerAnimated:YES completion:nil];
But still i can see some random screen from the app, comes up instead of black screen before my webview loads.
Any suggestions are welcomed. Thanks!!
Set in InterfaceBuilder or code white background color for UIWebView.
This code may help you:
- (void)viewDidLoad {
[super viewDidLoad];
webViewInstance.opaque = NO;
webViewInstance.backgroundColor = [UIColor clearColor];
//your remaining code here
}

iOS UITextField Outside of SuperView not Responding to Touches

I'm building an iOS 8 app that makes use of the new hidesBarsOnSwipe property on UINavgitationController to hide the nav bar while scrolling. At the same time that the nav bar hides, I'm also programmatically hiding the tab bar. On top of the tab bar, there is a text field which lets users comment on a post (much like Facebook). When the tab bar is hidden (by moving it downward and off the screen), the text field is moved down as well, so that it now sits at the bottom of the screen and so that there's no gap between the bottom of the screen and the text field.
So, things look great. But, turns out that the text field doesn't respond to touch events when it moves to the bottom of the screen. I did some digging and it appears that the reason is because the text field is outside of its superview (the view controller's view), and so touch events will not be sent to the text field.
So I think I've figured out why the issue is occurring, but I haven't yet figured out how to fix it. I've tried messing with hitTest:withEvent: and pointInside:withEvent: but didn't have any luck. Anyone have any solutions?
EDIT: Here is some code to make the question clearer (hopefully). When the nav controller's barHideOnSwipeGestureRecognizer is called, I am running the following code:
- (void)barHideSwipeGestureActivated:(UIPanGestureRecognizer*)gesture
{
[self animateTabBarUpOrDown:self.navigationController.navigationBar.frame.origin.y >= 0 completion:nil];
}
The method above is the following:
- (void)animateTabBarUpOrDown:(BOOL)up completion:(void (^)(void))completionBlock
{
if(!self.animatingTabBar && self.tabbarIsUp != up)
{
self.animatingTabBar = YES;
//to animate the tabbar up, reset the comments bottom constraint to 0 and set the tab bar frame to it's original place
//to animate the tabbar down, move its frame down by its height. set comments bottom constraint to the negative value of that height.
[UIView animateWithDuration:kTabBarAnimationDuration animations:^{
UITabBar *tabBar = self.tabBarController.tabBar;
if(up)
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y - tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = 0.0f;
}
else
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y + tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = -tabBar.frame.size.height;
}
} completion:^(BOOL finished) {
self.tabbarIsUp = up;
self.animatingTabBar = NO;
if(completionBlock)
{
completionBlock();
}
}];
}
}
Ok, finally found a solution for this one. I haphazardly messed around with changing the bounds of my view controller's view, but that was too hacky and ultimately didn't accomplish what I wanted it to.
What I ended up doing was changing my view controller's edgesForExtendedLayout property to be equal to UIRectEdgeAll which basically says that the view should take up the entire screen, and extend above top bars / below bottom bars.
I had to hack around a little bit with changing auto layout constraints on my text field so that it appeared in the right place at the right time, but overall, the solution was changing edgesForExtendedLayout to be UIRectEdgeAll - this makes the view take up the entire screen, so the text field is now still in the super view even when it animates downward, thus, allowing it to still receive touch events.

Finding The Center of a View

I have an iphone app with 2 ViewControllers . Both screens(viewcontrollers) show a loading screen. I create the loading screen programmatically:
UIView *loadingScreen = [[UIView alloc] initWithFrame:CGRectMake(100,200,144,144)];
loadingScreen.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
//{.. Other customizations to loading screen
// ..}
[self.view addSubview:loadingScreen];
For some reason, the second viewcontroller's loadingScreen is significantly lower and it isn't centered on the screen. The first viewcontroller works perfectly and is dead center like I want.
The second viewcontroller is a UITableView and it shows the uinavigationbar, whereas the first viewcontroller doesn't show the uinavigationbar. Also, I use storyboard for my app.
I've outputted to the NSLog self.view.frame.size.height and loadingScreen.center in both instances and THEY HAVE THE SAME COORDINATES! So, not sure why it is showing up lower. Any ideas why the second loadingScreen is lower and how to fix? Thanks!
You mention that one screen displays a UINavigationBar while the other does not. When you display a navigation bar, it offsets the rest of your view - in this case by shifting it down.
There are two quick fixes. You can either adjust your center point up by the size of the UINavigationBar (65 pts - unless it's a custom UINavigationBar and you've changed its size) or you can set the "Adjust Scroll View Insets" value to false in the attributes inspector.
The latter is probably the easiest and comes most recommended. Note though, that the top of your UITableView will now be underneath the UINavigationBar.
My final note would be that if you wanted to do it programmatically than in your UITableView's delegate you can call
- (BOOL)automaticallyAdjustsScrollViewInsets
{
return NO;
}

How to reset my UIScrollView's position after returning from a modal transition?

I have a simple view containing a long view with many buttons, with the whole thing being in a UIScrollView. The scroller works well, and I can scroll to the bottom and click a button. Every button triggers a modal seque to another view. That new view is then dismissed by user interaction, causing the original UIScrollView's view to load again.
Here's the problem: If I click on a button toward the top of the UIScrollView, I enter the modal segue, dismiss the new view, and return to the UIScrollView's view without a problem. But, if I click on one of the buttons toward the bottom of the UIScrollView, when I return seque out and then transition back, my scrolling is all messed up. I can only see the area beneath my scroller, and can't scroll back up to the top anymore!
I'm pretty sure there must be some way to reset the UIScrollView's starting and ending points upon ViewWillAppear, but I can't figure it out. Any help is appreciated!
Also, FYI, I simply added the UIScrollView through interface builder, and haven't implemented or synthesized it anywhere yet.
try this code:
- (void)viewWillAppear:(BOOL)animated
{
[yourscrollview setContentOffset:CGPointZero animated:YES];
}
Please note: the bug this question and answer is about appears to be fixed in iOS 7. The rest of this answer is only relevant to iOS 6 (and probably earlier).
The behaviour being exhibited here is a bug in the UIScrollView class. As noted by the OP, after returning from a modally presented UIViewController to a scene containing a UIScrollView, the UIScrollView takes whatever point it's currently scrolled to and starts behaving as though that is its origin. That means that if you'd scrolled down your scroll view before modally presenting another View Controller, you can't scroll back up upon returning to the scene with the scroll view.
The same thing happens when you remove the Scroll View from the view hierarchy and re-add it, even without changing its window.
You can work around this by setting the contentOffset of the scroll view back to {0,0} before it gets displayed again after dismissing the modal View Controller. If you actually want to preserve the point the user had scrolled to before they triggered the modal, then after the UIScrollView is redisplayed you can set the contentOffset back to whatever it was before you reset it.
Here's a UIScrollView subclass that fixes the bug without resetting the scroll view to the top whenever you return from a modal:
#interface NonBuggedScrollView : UIScrollView
#end
#implementation NonBuggedScrollView {
CGPoint oldOffset;
}
-(void)willMoveToWindow:(UIWindow *)newWindow {
oldOffset = self.contentOffset;
self.contentOffset = CGPointMake(0,0);
}
-(void)willMoveToSuperview:(UIView *)newSuperview {
oldOffset = self.contentOffset;
self.contentOffset = CGPointMake(0,0);
}
-(void)didMoveToWindow {
self.contentOffset = oldOffset;
}
-(void)didMoveToSuperview {
self.contentOffset = oldOffset;
}
#end
If you'd rather do this in a UIViewController than in a UIScrollView subclass, change the content offset in the viewWillAppear: and viewDidAppear methods.
If you don't want to preserve where the user's scroll position when they return from a modal, and just want to scroll the UIScrollView back to the top, as the OP asked for, then all you need is the even simpler:
#interface NonBuggedScrollView : UIScrollView
#end
#implementation NonBuggedScrollView
-(void)willMoveToWindow:(UIWindow *)newWindow {
self.contentOffset = CGPointMake(0,0);
}
-(void)willMoveToSuperview:(UIView *)newSuperview {
self.contentOffset = CGPointMake(0,0);
}
#end
First, thanks for the approved answer above. Someone mentioned that it was no longer applicable but I have a scrolling view inside of table view cell and it needs to be reset when the cell is reused.
Here is the solution in Swift.
#IBOutlet var scrollView: UIScrollView!
// many lines of code later inside a function of some sort...
scrollView.setContentOffset(CGPointMake(0.0, 0.0), animated: false)
To solve this problem i use this code:
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.scrollview scrollRectToVisible:CGRectMake(0, 0, 1, 1)
animated:NO];
}
You can change the starting and ending points by calling scrollRectToVisible:animated:. But I'm not sure if this fixes your problem.
Use below code snippet to restore the scroll position for a UIScrollview
Declare "scrollPosition" variable as CGPoint type.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
//get the current offset
scrollPosition = scrollView.contentOffset;
//set current view to the beginning point
self.scrollView.contentOffset = CGPointZero;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
//retrieve the previous offset
self.scrollView.contentOffset = scrollPosition;
}

Black bar flashes at top of UITableView when pushing to view with "Hides Bottom Bar When Pushed" in IB

This is a weird error that may just be an issue in Xcode for all I know. I have a tab bar controller where the first view is a UITableView with (obviously) a number of cells. When you select a cell, I've set up a segue on the MainStoryboard to go to a detail view controller. I want the tab bar to be hidden when I go to the detail view, so I went into the storyboard, chose my detail view, and clicked "Hides Bottom Bar on Push" in the editor screen that starts with "Simulated Metrics."
Everything works just fine, except that when I tap on a cell, a black bar flashes at the top of the UITableView screen, dropping the tableview cells down (as if the cells are falling down below the tab bar at the bottom), just before the screen pushes over to the detail view. The effect isn't harmful at all, but it's very disconcerting, and I'd like to smooth that out.
The only fix I've found is to uncheck the "Hides Bottom Bar when Pushed" option on the storyboard. That indeed does get rid of that black bar flash, but of course the tab bar stays on the screen when I go to the detail view, which is what I don't want.
Any ideas?
Just for completeness' sake, I went ahead and ran
[self.navigationController setToolbarHidden:YES animated: YES];
on the detail view controller's viewWillAppear method (and even tried it with the storyboard option both on and off), but there was no difference. The toolbar did indeed hide just fine, but I still got that black line at the top. So weird.
I know it is too late !!! I ran into same issue. It seems like the Auto resizing mask for the view was incorrect to be exact the UIViewAutoresizingFlexibleTopMargin. I checked this on in the xib file. If you are trying to do it in code make sure this flag -UIViewAutoresizingFlexibleTopMargin - is not included in the autoresizing mask.
Hope this will help some one in the future
I know it is a bit late, but I have same problem and I can't solve it with any of the previous answers. (I suppose this is the reason non was accepted).
The problem is that view size of the SecondViewController is same as view size of a previous ViewController, so too small to fit in a ViewController with Toolbar hidden. Thats why black background of a UITabBarController is visible at the top when transition is happening, and on a viewDidAppear view will stretch on right size.
For me it help to subclass root UITabBarController and set background color to same background color as SecondViewController has.
class RootViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Style.backgroundColor
}
}
Then you can leave checkbox checked inside storyboard and it will look ok.
P.S.
If you have some views, that is position on the bottom part of the view, you need to set bottom constraints so they are smaller by 49 (because this is the height of the toolbar), and then on viewDidAppear set the right constraint.
For example:
I have view that need to be position 44 px from bottom edge. Before, I have constraint set to 44 and I have some strange behaviour of that view. It was placed to height and then jump on the right place.
I fix this with setting constraint to -5 (44-49), and then in viewDidAppear set the constraint back to 44. Now I have normal behaviour of that view.
Wow I just had the same issue now, very painful, and no info on the net about it.
Anyway, a simple workaround for me was to change the current view's Frame moving the y coordinates up and making the height bigger by the height of the tab bar. This fixed the problem if done straight after pushing the new view onto the navigation controller. Also, there was no need to fix the Frame afterwards (it must be updated when the view is shown again).
MonoTouch code:
UIViewController viewControllerToPush = new MyViewController();
viewControllerToPush.HidesBottomBarWhenPushed = true; // I had this in the MyViewController's constructor, doesn't make any difference
this.NavigationController.PushViewController(viewControllerToPush, true);
float offset = this.TabBarController.TabBar.Frame.Height;
this.View.Frame = new System.Drawing.RectangleF(0, -offset, this.View.Frame.Width, this.View.Frame.Height + offset);
Objective C code (untested, just a translation of the monotouch code):
UIViewController *viewControllerToPush = [MyViewController new];
viewControllerToPush.hidesBottomBarWhenPushed = YES; viewControllerToPush.hidesBottomBarWhenPushed = YES;
float offset = self.tabBarController.tabBar.frame.size.height; float offset = self.tabBarController.tabBar.frame.size.height;
self.view.frame = CGRectMake(0, -offset, self.view.frame.width, self.view.frame.height + offset); self.view.frame = CGRectMake(0, -offset, self.view.frame.size.width, self.view.frame.size.height + offset);
Do this in viewWillAppear of detailViewController, it should work fine
subclass your navigation controller, or just find the navigation bar
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let backdropEffectView = navigationBar.subviews[0].subviews[0].subviews[0] //_UIBackdropEffectView
let visualEffectView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
visualEffectView.frame = backdropEffectView.frame
backdropEffectView.superview?.insertSubview(visualEffectView, aboveSubview: backdropEffectView)
backdropEffectView.removeFromSuperview()
}

Resources