CATransform3D weird UIScrollView contentOffset - iphone

I'm doing a cool CA3DTransform while a UIScrollview is scrolling in the scrollViewDidScroll delegate method. It works perfectly when you use your finger to scroll, so when scrolling manually everything is perfect.
But, when I set scrollview contentoffset programmatically like:
[scrollView setContentOffSet:CGPointMake(0,460) animated:YES];
It still calls the delegate method scrollviewdidscroll, so the same animation methods are called, so I still see the correct animation, BUT, somehow parts of the view are missing during and after the animation! I've tried to set the layer.zPosition on all things and it doesn't seem to help. It should be fine since manually scrolling does work without parts of views going missing... Somehow calling this method programmatically differs and I have no idea why!

Post your question in this way is not very clear, considering that with the delegate scrollViewDidScroll, I can print me the code, either manually or programmatically.
...
UIScrollView *sv = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 10, 320, 200)];
[sv setContentSize:CGSizeMake(640, 200)];
sv.delegate = self;
[sv setContentOffset:CGPointMake(320, 0) animated:YES];
[self.view addSubview:sv];
...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"---%#---", NSStringFromCGPoint(scrollView.contentOffset));
}
In this simple example, you can see that the delegate actually works.
Excuse the simplicity of the answer, but the only question you ask is hard to give you help.

I had a similar problem (in the context of using a scrollview for a slideshow) but solved it by using scrollRectToVisible:NO (NO for no animations), and wrapped that inside a UIView animation block where I also put my animation code.

Related

Programmatically zooming into small area of larger picture using UIScrollView

I'm trying to get my code to programmatically zoom into a small area of the larger picture. I'll add the tap code later, but right now I just want to see it work.
The zoomToRect in this code does absolutely nothing and I simply don't understand why. When I build it, the image just sits there at the 0,0 origin.
I've tried using:
setContentOffset and scrollRectToVisible and both these work fine -the image moves to the specified coordinates. But neither of these is what I want, because I need to move and zoom the image, not just move it.
But zoomToRect utterly refuses to do anything. I've read about 50 pages of examples and tutorials on this now and not a damn thing works. I'm tearing my hair out not knowing why. Clearly I'm missing some really fundamental or important point.
UIImage *myFirstImage = [UIImage imageNamed:#"manga_page.jpg"];
UIImageView *myFirstImageView = [[UIImageView alloc] initWithImage:myFirstImage];
[myFirstImageView setFrame:CGRectMake(0, 0, myFirstImage.size.width, myFirstImage.size.height)];
UIScrollView *myFirstScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[myFirstScrollView setContentSize:CGSizeMake(myFirstImage.size.width, myFirstImage.size.height)];
[myFirstScrollView addSubview:myFirstImageView];
[self.view addSubview:myFirstScrollView];
[myFirstScrollView zoomToRect:CGRectMake(300, 300, 300, 300) animated:YES];
The problem was that I hadn't defined the delegate for zooming - see viewForZoomingInScrollView (bottom of page) here:
https://developer.apple.com/library/ios/documentation/uikit/reference/uiscrollviewdelegate_protocol/Reference/UIScrollViewDelegate.html#//apple_ref/occ/intfm/UIScrollViewDelegate/viewForZoomingInScrollView
I don't have a code fragment in Objective C as I switched to the Xamarin framework. However the C# solution is as follows:
scrollView.ViewForZoomingInScrollView = delegate (UIScrollView sv) { return imageView; };
Where imageView is the UIImageView containing the image to be scrolled around.
Set the maximumZoomScale and minimumZoomScale properties for the UIScrollView.

UIScrollView's setContentOffset called before new subview is loaded

I have a UIView that I'm sliding in using a UIScrollView. Code looks like this:
[view setObject:object]; // updates a bunch of labels, images, etc
[scrollView addSubview:view];
[scrollView setContentOffset:CGPointMake(320,0) animated:YES];
Problem is, the scrollView doesn't seem to wait for the view to be completely loaded before animating its contentOffset, and so the animation is kinda jerky (almost non-existent on the first slide-in and on older devices). Oddly enough, switching the 3rd line to this fixes it:
[UIView animateWithDuration:0.3 animations:^{
[scrollView setContentOffset:CGPointMake(320,0) animated:NO];
}];
No lag whatsoever, perfectly smooth slide-in. However, this doesn't trigger any of the UIScrollViewDelegate calls, on which I also depend (but which aren't responsible for the lag).
Any idea of how I could tell the UIScrollView to wait for the view to be completely loaded before animating its contentOffset? Or maybe there's something else that I'm missing?
EDIT: before anyone else suggests it: yes, I did try:
[self performSelector:#selector(slideIn) withObject:nil afterDelay:1];
just to see if it would fix it. And yes it does fix the lag, but this is not an actual solution. performSelector:afterDelay: is never a solution, it's only a superficial fix. Plus, you're making the user wait extra seconds every time (since the actual delay may be much shorter than 1 second depending on the device model).
Performing selector after delay 0.01 works because the invocation is scheduled on the runloop vs. calling the method immediately. For that matter, afterDelay:0.0 may also work. Did not try in this particular case but works in may similar situations.
have you tried starting the scrolling in the "viewDidAppear" function of the viewcontroller... maybe this is called when the loading is finished.
otherwise try afterdelay with a very short time like 0.01 .. so that the call is scheduled next after the current work is done.
Try this, set contentSize after some delay (here delay is 2 second).
[view setObject:object];
[scrollView addSubview:view];
[self performSelector:#selector(contentSize) withObject:nil afterDelay:2];
}
-(void) contentSize {
[scrollView setContentOffset:CGPointMake(320,0) animated:YES];
}

UIScrollView scrollRectToVisible isn't doing anything

I'm not really sure why this isn't working, hopefully you can help me find the missing piece. I have a UIScrollView with paging enabled. I'm using it for side-scrolling through a tutorial. I have a button that when tapped should scroll the user back to the beginning of the tutorial. I originally tried using the scroll view's frame as the rect to scroll to because that CGRect should represent the first page. I've tried a couple of different CGRects to no avail though.
- (IBAction) touchedButtonReturnToBeginning:(id)sender {
// I've tried several CGRect's, none of which cause the UIScrollView to move.
// CGRect beginning = self.containerScrollView.frame
// CGRect beginning = self.containerScrollView.bounds;
CGRect beginning = CGRectMake(0, 44, 1, 1);
[self.containerScrollView scrollRectToVisible:beginning animated:YES];
}
I have verified that self.containerScrollView is hooked up in my xib as well as the touchedButtonReturnToBeginning action is connected to my button. I've used my debugger to step through this method, so I have verified that it is getting called. All of the variables are appropriately set, but when I call scrollRectToVisible the scroll view just doesn't do anything.
Any ideas?
I don't know why that wouldn't work, but have you tried [self.containerScrollView setContentOffset:CGPointZero animated:YES]?
To make scrollRectToVisible works check your self.containerScrollView.contentSize. It should be big enough :)

iPhone OS: Tap status bar to scroll to top doesn't work after remove/add back

Using this method to hide the status bar:
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
When setting "hidden" back to NO, the tap-to-scroll-to-top (in UIWebView, UITableView, whatever) doesn't work any more, and requires a restart of the app to get the functionality back.
Is this a bug (I filed a rdar anyhow) or have I missed a step? Should I perhaps expect this behavior since the statusBar "loses touch" somehow with the respective view?
You could try setting the ScrollsToTop property to true again after re-showing it:
[currentView setScrollsToTop:YES];
If that's not working, are you definitely only showing one view? If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored...
In iOS 5.0 you can access the scrollview property of the UIWebView
webView.scrollView.scrollsToTop = YES;
The following fix by Alex worked for me. Thanks!
((UIScrollView *)[[webView subviews] objectAtIndex:0]).scrollsToTop = NO;
Being in a hurry this fix worked great, however given more time I might've subclassed the UIWebView and accessed the protected UIScrollView member directly.
The worry I have with Alex' method is that it assumes that UIScrollView is at index zero of the subviews (encapsulation allows private members to change). Which suggests another solution still:
for (UIView* v in [webView subviews])
{
if ([v isKindOfClass:[UIScrollView class]])
{
(UIScrollView *)v.scrollsToTop = NO;
}
}
I was having a similar problem where the scroll-to-top functionality was lost. Turns out this will only work when you have only one active view at a time (within the same scroll view). In my case I had a table view and another view which would fade in/out. Adding a removeFromSuperview at the end of the animation did the trick.
The answer was in the UIScrollView.h file comments:
/*
this is for the scroll to top gesture. by default, a single scroll visible scroll view with this flag set will get the call. if there is more than one visible with this
flag set or the delegeat method returns NO, the view isn't scrolled
*/
#property(nonatomic) BOOL scrollsToTop; // default is YES. if set, special gesture will scroll to top of view after consulting delegate
You can use the following code to have the UIWebView ignore scrollToTop without the extra UIScrollView:
((UIScrollView *)[[webView valueForKey:#"_internal"] valueForKey:#"scroller"]).scrollsToTop = NO;
I had a similar problem after playing a Youtube video within my app. scrollsToTop was still set to YES but tapping the status bar had no effect.
I finally realised that my app window was no longer the key window. After adding the following line to a UIWindow subclass (which I already had for other reasons) everything worked as it should again:
if (![self isKeyWindow]) [self makeKeyWindow];
I just ran across a similar behavior in the app I'm currently working on. In its case, if you load a YouTube video from within a UIWebView, scroll to top stops working for the rest of the application's life cycle. I kind of assume this might happen after loading the movie player as well, but haven't confirmed. That functionality has been around a lot longer and probably has fewer bugs.
When there are multiple scrollview, you can also set scrollUpToTop to NO for the others scrollview. cf:
setScrollsToTop with multiple UIScrollView classes and/or subclasses(UITableView)
I want to add my case, I add an UIWebView on an UIScrollView, as h4xxr had answered on the top:
If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored
So, I get a simply way to make it work on webView: just set the scrollView·s scrollsToTop property false.
And when tap the status bar, it won`t got intercepted by the scrollView, and the webView scrolls to the top!
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = self.view.bounds;
scrollView.scrollsToTop = false; //igore scrollView`s scrollsToTop
[self.view addSubview:scrollView];
UIWebView *webView = [[UIWebView alloc] init];
webView.frame = scrollView.bounds;
[scrollView addSubview:webView];

How to add controls beneath a UIWebView

What's the simplest, fastest, or otherwise (objectively) best way to add a couple of controls (e.g. some UIButtons) beneath a UIWebView?
To be clear, I'd like to display a regular scrolling UIWebView, but when scrolling reaches the bottom I'd like there to be some UIControls after the web content. I'd rather not fake it with HTML/CSS content that looks like a control; ideally they should be real controls that I can hook up to my view controller as usual.
I don't know whether it's easiest to try to do this with one big UIScrollView containing both the UIWebView and the controls (will there be scrolling conflicts between the UIScrollView and the nested UIWebView?), or with a UITableView containing the web view and controls in separate cells (will performance be terrible?), or in some other way that I haven't thought of.
Update: I don't imagine that I want the UIWebView itself to actually scroll; I thought I'd need to resize it to accommodate all of its content, disable its scrolling behaviour entirely, and then allow the user to scroll its parent view (either the UIScrollView or the UITableView) to reveal different parts of the UIWebView.
Neither the UIScrollView nor UITableView solutions you proposed will work.
If you put the UIWebView in a UIScrollView, one or the other will intercept the scrolls and scroll its content pane, but the other will not. I'm not sure which one will, I think it would be the UIScrollView. In any case, there is no provision for the UIScrollView to scroll when you reach the end of scrolling in the UIWebView, or vice versa.
If you use a UITableView, the UIWebView will scroll inside its cell, but the UITableView won't scroll at all, or the UIWebView won't scroll at all, and the UITableView will. If you use the 'grouped' table style, it's possible that you could scroll the UIWebView and the UITableView, but it would not be the cohesive sort of experience you are looking for.
The only technique I can think of that might work is to muck around in the internals of the UIWebView and add your UIView containing buttons and whatnot to the scrolling entity used by the UIWebView. This is beyond my ken, but these header dumps will get you started. Keep in mind that if Apple notices what you're doing, they may reject your app, and additionally, the internals may change at any time, breaking your code. If you go this route, try to code it in such a way that missing functionality will cause a graceful failure.
EDIT:
With some discussion and experimenting, the OP and I have come to the conclusion that it is very possible, and there is a way to do it that doesn't rely on internals at all. That is, use an enclosing scroll view, and resize the web view once it has loaded to the full size of its own content view. This size can be found using [webView sizeThatFits:CGSizeZero].
If you did want to use UIWebView's privates to accomplish this, I have a short code example of adding a view to the UIWebView's document view, since I got it all worked out anyway :)
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
UIView* testView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)] autorelease];
UIWebView* testWebView = [[[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)] autorelease];
[testView addSubview:testWebView];
[testWebView setDelegate:self];
[testWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com"]]];
[window addSubview:testView];
[window makeKeyAndVisible];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
UIView *insert = [[[UIView alloc] initWithFrame:CGRectMake(0, [[webView _documentView] frame].size.height, 320, 40)] autorelease];
[insert setBackgroundColor:[UIColor greenColor]];
[[webView _documentView] addSubview:insert];
}
I don't believe there is any way to do this within the constraints of the public SDK.
This is how i do it.
- (void)webViewDidFinishLoad:(UIWebView *)webView {
for(UIView *view in [webView subviews]){
if([view isKindOfClass:[UIScrollView class]]){
NSLog(#"Scrollview found with tag %d", [view tag]);
UIScrollView *scroll= (UIScrollView *)view;
[scroll addSubview:self.finishBtn];
[finishBtn setCenter:CGPointMake(webView.center.x, scroll.contentSize.height-30)];
}
}
}