UIScrollView with UIWebViews

前端 未结 1 1217
长发绾君心
长发绾君心 2021-02-06 19:14

There seem to be many questions asked about this subject here on stackoverflow, but none of them touch on the updates made in 3.0. After mucking around for hours on end I finall

1条回答
  •  南方客
    南方客 (楼主)
    2021-02-06 19:29

    EDIT: this is completely broken on iOS 4.0. I will update this once I figure out what's wrong.

    Since UIWebView doesn't play very nicely with the automagical nested scrolling introduced in 3.0 and sending touchesBegin/Moved/Ended to a UIScrollView is no longer supported here's what I came up with.

    I added a transparent UIView subclass on top of all of the other views and made it catch all touches. When touches begin I forward that to the currently active UIWebView and start a short timer (or rather another thread that uses usleep() to sleep for a little bit - in my experience the main thread can get locked up when a lot of touch events are incoming and timers can go way off).

    In touchesMoved I check if the timer I started hasn't expired - if it hasn't and fabs(location.x - lastLocation.x) > fabs(location.y - lastLocation.y) then it looks like the user is trying to page (this could be adjusted with a multiplier, but for now this seems to be just the sweet spot). If it's been determined that the user is trying to page I send the web view touchesCancelled and start adjusting the scroll view's contentOffset.x accordingly.

    In touchesEnded, if it's been determined that the user was paging, I apply some Newtonian physics to see if the touch, along with some kinetic continuation of the scrolling would've been enough to cross over to the next page (more than half of the next page is visible). If so I animate the scroll to continue based on the speed of the scroll.

    Warning: This code is horrible due to trying a billion different things. I haven't had a chance to clean it up yet. Hope this helps someone.

    -(void)timer {
        usleep(250000);
        expired = YES;
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        paging = NO;
        expired = NO;
        determined = NO;
    
    
        if(navManager == nil) {
            navManager = [[[[UIApplication sharedApplication] delegate] viewController] navManager];
        }
    
        UITouch *touch = [touches anyObject];
        CGPoint location = [touch locationInView:self];
    
        touchStartX = location.x;
        touchStarted = touch.timestamp;
    
        [NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil];
    
        [[navManager.currentNavItem.webView hitTest:CGPointZero withEvent:event] touchesBegan:touches withEvent:event];
    }
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
        if(!paging) {
            [[navManager.currentNavItem.webView hitTest:CGPointZero withEvent:event] touchesCancelled:touches withEvent:event];
        }
    
    }
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
        if(!paging) {
            [[navManager.currentNavItem.webView hitTest:CGPointZero withEvent:event] touchesEnded:touches withEvent:event];
        }
        else {
            UITouch *touch = [touches anyObject];
            CGPoint location = [touch locationInView:self];
            NSTimeInterval touchLasted = touch.timestamp - touchStarted;
            CGFloat touchLen = location.x - touchStartX;
            float dir = touchLen/fabs(touchLen);
            float touchSpeed = touchLen/touchLasted;
            float deAccelRate = -3000.0;
            float timeToDeAccel = (-touchSpeed) / deAccelRate;
            float averageVelocity = touchSpeed / 2.0;
            float couldTravel = averageVelocity*timeToDeAccel;
    
            if(couldTravel > navManager.scrollView.frame.size.width/2.0) {
                couldTravel = navManager.scrollView.frame.size.width/2.0;
            }
            couldTravel = dir*couldTravel;
    
            NSLog(@"could travel: %f, touchSpeed: %f, timeToDeAccel = %f, averageVelocity: %f", couldTravel, touchSpeed, timeToDeAccel, averageVelocity);
    
            int page = round((navManager.scrollView.contentOffset.x - couldTravel) / navManager.scrollView.frame.size.width);
            if(page < 0) 
                page = 0;
            else if(page > round(navManager.scrollView.contentSize.width / navManager.scrollView.frame.size.width) - 1) 
                page = round(navManager.scrollView.contentSize.width / navManager.scrollView.frame.size.width) - 1;
    
            CGPoint newOffset = CGPointMake(page*navManager.scrollView.frame.size.width, navManager.scrollView.contentOffset.y);
            float needToMove = fabs(newOffset.x - navManager.scrollView.contentOffset.x);
            float timeToAnimate = needToMove / averageVelocity;
    
            [UIView beginAnimations:nil context:NULL];
            [UIView setAnimationDelegate:nil];
            [UIView setAnimationDuration:timeToAnimate];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
            navManager.scrollView.contentOffset = newOffset;
            [UIView commitAnimations];
        }
    }
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
        UITouch *touch = [touches anyObject];
        CGPoint location = [touch locationInView:self];
        CGPoint lastLocation = [touch previousLocationInView:self];
    
        if(!determined && !expired) {
    
            if(fabs(location.x - lastLocation.x) > fabs(location.y - lastLocation.y)) {
                NSLog(@"PAGE!!");
                paging = YES;
                [[navManager.currentNavItem.webView hitTest:CGPointZero withEvent:event] touchesCancelled:touches withEvent:event];
            }
            else
                [navManager.scrollView touchesCancelled:touches withEvent:event];
    
            determined = YES;
        }
    
        if(!paging) 
            [[navManager.currentNavItem.webView hitTest:CGPointZero withEvent:event] touchesMoved:touches withEvent:event];
    
        else {
            float xScroll = navManager.scrollView.contentOffset.x-(location.x - lastLocation.x);
    
            CGPoint newOffset = CGPointMake(xScroll, navManager.scrollView.contentOffset.y);
            navManager.scrollView.contentOffset = newOffset;
        }
    }
    

    There's a few more things to add to make it feel more native, but this should be a good starting point.

    0 讨论(0)
提交回复
热议问题