UIScrollView works as expected but scrollRectToVisible: does nothing - iphone

I have used UIScrollView before, and am using it now, and never had a problem. I'm now adding it to an old app, and while it works as expected (I can look at the contents, scroll around with my finger, all the bounds and sizes are setup right so there is no empty space in the content, etc.), I just can't get scrollToRectVisible to work. I have even simplified the call so that it merely moves to the 0,0 position:
[scrollView scrollRectToVisible:CGRectMake(0, 0, 10, 10) animated:YES];
or move it to 0,200:
[scrollView scrollRectToVisible:CGRectMake(0, 200, 10, 10) animated:YES];
I even made a quick app to test this and I can get scrollRectToVisible to work there as I expect. But in my old app, I can't seem to make it do anything.
I can make the scrollView scroll with setContentOffset:, but that's not what I want.
This scrollView and its contents are defined in the nib by IB and used with an IBOutlet. The only code I am using in my app to handle the scrollView is
[scrollView setContentSize:CGSizeMake(scrollView.contentSize.width, imageView.frame.size.height)];
(I'm only interested in vertical scrolling not horizontal).
Has anyone run into a problem like this?
I have compared the scrollView attributes in both apps and they are identical.
ADDENDUM:
My scrollViews frame is: 0.000000 0.000000 480.000000 179.000000
My scrollViews contentSize is: 0.000000 324.000000
It still acts like the rect I want to scroll to is already visible and no scrolling is needed. Not sure if that is what is happening. This is just the darnest thing. Seems like such an easy thing to resolve...
ADDENDUM #2:
This is how I am making do without scrollRectToVisible:
CGPoint point = myRect.origin;
if (![clefScrollView pointInside:point withEvent:nil]) {
point.x = 0;
if (point.y > clefScrollView.contentSize.height - clefScrollView.bounds.size.height)
point.y = clefScrollView.contentSize.height - clefScrollView.bounds.size.height;
[clefScrollView setContentOffset:point animated: YES];
}
Everything else about this scrollView works as expected, but scrollRectToVisible. WHY?!? Any wild guesses?

Over a month later, and I finally figured it out. While the alternative above worked well, it has been bugging me and I had to finally figure it out. Turns out that it was somewhat of a stupid mistake.
Here's the old code in my viewDidLoad, where I set up the scrollView:
[clefScrollView setContentSize:CGSizeMake(clefScrollView.contentSize.width, clefView.frame.size.height)];
The value of a scrollView height or width can't be 0! I think this got past me because I was assuming that ScrollView sizes start out with a valid size, and I was missing the fact that one dimension was zero!
This works:
[clefScrollView setContentSize:CGSizeMake(clefView.frame.size.width, clefView.frame.size.height)];

Yeah, I have not had success with scrollRectToVisible:animated:, but setContentOffset:animated: always works for me. Out of curiosity, why do you not want to use setContentOffset:animated:? It seems to be the proper solution.

You might want to check and see that the scrollView's delaysContentTouches property is set to NO.
If you call the method scrollRectToVisible or setContentOffset from a touch within a subview of your scrollView then your scrollView could be holding things up by delaying touches.
So, make sure you set the following in your scrollView.
scrollView.delaysContentTouches = NO;
You will most likely be able to move the scrollView with and without animation set to YES.

i had it like this and it didn't work
scrollView.contentSize = CGSizeMake(scrollView.contentSize.width, someKnownValue);
i was just changing the height of the contentSize, so I didn't think that would give me any problem but it did ...
had to change it to this
scrollView.contentSize = CGSizeMake(someOtherKnownValue, someKnownValue);
turns out scrollView.contentSize.width is not necessarily set to a valid value from the get go, so better give it an specific value

The suggested solution above may still give an error if you haven't set the frame for the "clefScrollView".
If the solution above is used and still having the same problem, make sure you have initialized your scollView (in this case clefScrollView) frame prior to setting the content view.
clefScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,320,450)];
[clefScrollView setContentSize:CGSizeMake(clefView.frame.size.width, clefView.frame.size.height)];

A little late to the game but I was having the same problem. Though I absolutely had my scrollview's content size set correctly - even excessively so for the height value. Even after extensive checks to validate the frames and content sizes - still wasn't working. I had to adjust the "bounds" frame height, then everything worked great.

I had the same symptoms as this issue..the scrollRectToVisible was not working as intended. My problem was that the scrollview on the storyboard had a ambiguous constraint problem. Once that was fixed the call worked just fine. It is something else to check when the scrollRectToVisible call isn't working as intended.

For me the problem was that a constraint in a subview was not explicit.
Check that every constraint in your content is set, even if you are not needing it apparently for the layout.

Related

UITableView not scrolling after switching to iOS 7

In iOS 6, I had a UITableView created using QuickDialog in my app. It scrolled normally. When I switched to iOS 7, the same UITableView does not scroll properly. I can drag to the bottom (the scroller compresses) but when I release, it pops back up the to the top. I've been playing with viewDidAppear to try and diagnose the problem. See the code block below.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"Content height: %f",self.quickDialogTableView.contentSize.height);
[self.quickDialogTableView reloadData];
NSLog(#"Content height: %f",self.quickDialogTableView.contentSize.height);
[self.quickDialogTableView layoutIfNeeded];
NSLog(#"Content height: %f",self.quickDialogTableView.contentSize.height);
}
The output of this block in iOS 7 is
Content height: 0.000000
Content height: 836.000000
Content height: 0.000000
Meanwhile, the output of this block in iOS 6 (simulator) is
Content height: 836.000000
Content height: 836.000000
Content height: 836.000000
Also to try and diagnose the problem, I set up a button that would trigger [self.quickDialogTableView reloadData]. Whenever that button is pushed, the scrolling behavior begins to function normally. Then when I leave the view and come back, the scrolling fails again (until the button is pushed). To be clear, I have tried to put a reloadData in viewWillAppear by itself (i.e., removing the last two lines in the code block above) and it does not correct the scrolling.
I'm looking for clues as to where I might look to correct the issue. Thanks in advance for any help.
Okay, so I couldn't figure out the source of the problem but I did find a workaround that I hope helps someone else. Or at least, maybe helps someone else point out what's really wrong.
I created a property called trueContentSize where I store what the correct size is.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.quickDialogTableView reloadData]; // Calculates correct contentSize
self.trueContentSize = self.quickDialogTableView.contentSize;
}
Then, in -viewDidLayoutSubviews I correct the contentSize manually.
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.quickDialogTableView.contentSize = self.trueContentSize;
}
Just thought I might throw in my two cents here..
Same problem was happening to me. Table loads fine at first, but navigate to a different screen, come back, and the table view's contentSize is 0,0 and unscrollable. Fausto's workaround wasn't reliably working, either.
What turned out to be the case for me was just the act of referencing topLayoutGuide in -viewWillLayoutSubviews. Doing that causes all of the above symptoms. Try it out in a new project:
Setup a table view controller inside a tab controller.
Give it 30 rows with just static content.
Run and you can scroll. Switch tabs, then back, and you can scroll.
Add NSLog(#"%#", self.topLayoutGuide); in -viewWillLayoutSubviews
Run again, you can scroll, but switch tabs, no more scrolling.
Weird. Sounds like an iOS bug, or you just shouldn't reference topLayoutGuide in that method. Removing any reference to that property will fix the issue.
Edit: As of earlier this year, Apple confirmed via a Radar report I made this is an iOS bug. Don't reference topLayoutGuide until it's been resolved (whenever that will be, heh). :)
This is my current temporary fix. Anyone figure out the real fix?
#interface UITableView (Extension)
#end
#implementation UITableView (Extension)
- (void)setContentSize:(CGSize)contentSize {
if (contentSize.height != 0) {
[super setContentSize:contentSize];
NSLog(#"set content height %f", contentSize.height);
} else {
NSLog(#"set content size zero");
}
}
#end
Faced the same problem and the only solution found was to reload the data in the table. Hope that helps.
I had a similar issue. My issue was that I had an explicit height set for the table view in the storyboard. Updating my table view to make use auto layout by setting distance to the views above / below made it work for me.

iOS - Positioning UIViews

I have a UIView and in interface builder I have set it's location to 160, 57.
I at some point during the apps life cycle, the UIView may move, and so I have a button to put it back to where it was before. So I use the following code:
[self.myView setCenter:CGPointMake(160, 57)];
However, when the UIView moves back, it's not in exactly the same place as it was when the app first loaded. It's a tiny tiny bit to one side.
Any idea why this is? Thanks in advance.
Try setting the view's location using view.bounds.origin instead of view.center.
I have solved my problem by using:
[self.myView setCenter:CGPointMake(159.5, 57)];
I didn't realise CGPointMake would accept floating point values so I thought I had to use 159 (too little) or 160 (too much). 159.5 was just right though.
It still doesn't explain why 160 works in Interface builder but I can just set this value when the view loads anyway.
Thanks for everyone's answers.

CATransform 3D with a modified .m34 breaks view hierarchy/ordering in iOS6, but not the view it was applied to

Foreword, this isn't me losing a view off screen because I did the transform wrong, it's weirder.
Problem is, if I use an .m34 transform to achieve the perspective I need, the view hierarchy breaks, but remove the transform and it draws everything correctly.
Here's an example.
I have a background image (subviewOne), a menu(subviewTwo), and an object on top of all of that which I apply the CATransform3D to (subviewThree).
Simple code:
CALayer *layer = subviewThree.layer;
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = -1.0 / 500;
layer.transform = perspectiveTransform;
Prior to applying this code, the view hierarchy was, and still is on iOS 5:
(bottom to top)
subviewOne->subviewTwo->subviewThree
After applying it, I end up with:
(bottom to top still)
subviewTwo->subviewOne->subviewThree
Now, subviewThree still has the perspective transform applied to it, and is in the correct spot, on top of everything else, same as on iOS5. However, the Menu/subviewTwo, is now hidden by the background image/subviewOne, and nothing I do will get it to be drawn on top of the subviewOne. No amount of insertSubviewAtIndex:, bringSubviewToFront, sendSubviewToBack, etc, will make the view draw correctly.
This is incredibly peculiar particularly because the views that are drawn out of order are NOT having any kind of CATransform3D applied to them.
I have verified this independently in two different apps and multiple devices 6 devices. iOS5 draws everything correctly, and if I remove those four lines, everything draws correctly, but nothing I've tried on iOS 6 stops the .m34 from breaking the view ordering. It's not always as simplistic as the example I've provided, but this is the most demonstrable case I have witnessed.
Has anyone else experienced this, solved this?
Edit: More info for comment.
Yeah, typo with the extra *.
Figure there's an Imageview, QuadCurve Menu, and Textview.
I was calling the method with the .m34 in the viewDidLoad, but swapped it to the viewDidAppear real quick to check for you.
Doesn't matter. Don't get me wrong, the subviews are listed in the correct order when you call
NSLog(#"%#", [self.view.subviews description]);
They just aren't drawn on screen correctly.
In desperation, I wrote some crazy weird code, and I discovered the following.
I can call the method that draws the menu on a 10 second delay,
[self performSelector:#selector(createQuadCurveMenu) withObject:nil afterDelay:10];
which ends in
[self.view addSubview:menu]
As well as a totally superfluous
[self.view bringSubviewToFront:menu]
and it's still drawn behind an imageView that is set as the lowest subview in the .xib.
I have verified this two ways. I can go into the .xib and set the imageView to hidden, and running again I can see the menu, now that the imageView isn't covering it. I can also just comment out the code that applies the .m34 transform to the textView, and the menu then again correctly appears on top of the imageView. Again, none of this happens on iOS5 and iOS4.
At this point, I'm starting to think that it's a bug inside iOS6 itself, and have been waiting for the NDA to expire so I can ask here if anyone else has experienced it.
Pretty sure this is an iOS 6 bug: I've blogged about it here: iOS 6 Rendering Bug: 3D-Rotation Causes Layers to Render Without Respect for View Hierarchy.
Good news: You can work around the bug by setting zPositions on the affected layers: set the zPositions in increasing order of the view hierarchy. So if I've understood correctly, you want:
subviewOne.layer.zPosition = 0;
subviewTwo.layer.zPosition = 1000;
subviewThree.layer.zPosition = 2000;
Check out the blog for more info, including a link to the Open Radar version of the bug I've logged with Apple.
Also, this might be considered a duplicate of this post: iOS 6 view hierarchy nightmare. It has the same bug source and solution, although the symptoms you both describe are different.

How do yo move a UIWebView offscreen?

How do you move a UIWebView offscreen?
Is that determined by the CGRect? Or some other property of UIView or UIWindow?
You can try:
[webView setFrame:CGRectMake(320.0f, 0.0f, 320.0f, 480.0f)];
or
[webView removeFromSuperview];
The first one just move it offscreen, you just cannot see it. The second one remove it from superview, maybe it still exist in memory, or maybe it'll removed when received memory warning.
UIWebView is a subclass of UIView (as are most visible objects in cocoa touch) which means you can use setBounds or setFrame in order to 'move' the view. If you use negative or large positive numbers the view will be moved offscreen.
Do remember that things offscreen also take up memory ;-)
Yes, you can do that. You could change the frame property of UIWebView to some coordinates which are off the screen.
You could do this but if you are done with the UIWebView & simply want to close it then this might not be the best solution as the memory allocated is still not released. First animate it off the screen then release it to free the memory. To release it do [YourWebView removeFromSuperview]; [YourWebView release]; YourWebView=nil;.
You can hide it by setting the hidden property to YES.
You can also move it off the screen with the frame, as you mentioned. A good place to start is CGRectMake(self.superview.frame.size.width,self.superview.frame.size.height,...) though it won't do well with rotations.
You can also set the alpha to 0, which is akin to making it hidden.
You can also send it the method removeFromSuperview.
There may be more ways.
Enjoy,
Damien

Animation inside a UIScrollView

I want to fade-out a view as it is scrolling inside a parent UIScrollview. When the fade-out animation begins, the scroll view stops scrolling. It jumps to the correct position when the fade is complete.
My fade-out is achieved with animateWithDuration and block objects, triggered upon a page-change I detect in scrollViewWillBeginDragging.
Does anyone know how to make them both happen simultaneously? Just to be clear, I am not 'animating' the UIScrollView scrolling - rather it is happening via user interaction of swiping.
EDIT:
Here is the code I'm using to fade the UIView. This code is in a UIViewController derived class, which is the delegate for a UIScrollView. When the user starts dragging his finger, I want to fade out the subView. But when the user starts draggin a finger, the subview fades and the scrolling stops. After the subView has completely faded out, the the scroll view will then snap to the location where the user's finger is.
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView
{
[UIView animateWithDuration:0.5
animations:^
{
self.subView.alpha = 0.0f;
}
completion:^(BOOL finished) { }];
}
A little late, but if you want to keep using blocks, you can use:
animateWithDuration:delay:options:animation:complete:
add "UIViewAnimationOptionAllowUserInteraction" to options to allow interaction while scrolling.
I'm sure that you will still have the lag problem. Here's the best way I can explain it. Please forgive me in advance since I'm probably using the wrong terms. All animations must run on the main thread. When you call an animation, iOS first *P*rocesses then it *R*enders before it generates *F*rames. It looks like this.
PPPPRRRRFFFFFFFFFFFFFFFFFF
But since ScrollViews don't know how long your animation is going to be or when it will end, it has to perform the animation like this.
PRFPRFPRFPRFPRFPRFPRFPRF
My theory is that the lag you are experiencing has to do with these two calls colliding on the main thread at the same time. I'm not sure how you would solve this problem other than with a faster chip. I've that you could push one animation to the CPU and one to the GPU, but I'm not that advanced at programming yet.
very interesting ... I've checked this out, and yes, i have the same effect ... Well, it seems that the animateWithDuration somehow blocks the main thread ... which is not logical, and the documentation doesn't say anything about it either ..
However there is an easy workaround, something similar to this: (i've set the animation duration to 3 so i can see that it's working while i'm moving my scroll view :) ...)
[UIView beginAnimations:#"FadeAnimations" context:nil];
[UIView setAnimationDuration:3];
self.subview.alpha = 0.0f;
[UIView commitAnimations];
I would suggest, since the opacity is based on the user's finger's movements in the UIScrollView, using the delegate method scrollViewDidScroll:. The scrollView passed as a parameter can be used to check the contentOffset which is simply a CGPoint indicating how far into the content view of the UIScrollView the user has scrolled. Something like this can be used to relate the scroll position to the opacity of a given view in a paginated UIScrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// The case where I used this, the x-coordinate was relevant. You may be concerned with the y-coordinate--I'm not sure
CGFloat percent = ((int)(scrollView.contentOffset.x) % (int)(scrollView.frame.size.width)) / scrollView.frame.size.width;
if (percent > 0.0 && percent < 1.0) { // Of course, you can specify your own range of alpha values
relevantView.alpha = percent; // You could also create a mathematical function that maps contentOffset to opacity in a different way than this
}
}
According to information that is still not supposed to be widely released, all iOS 4.x versions completely block user interaction while the animation is in progress.
Isn't it interesting, though, that you're UITouches are obviously still registered during the animation? Hmm... maybe that HINTS that something NEW is coming in a yet-to-be-released version!
I.e., If you can, read the iOS 5 Beta documentation on UIView class methods.