I need to have two UIImageView, both with zoom enabled, (I can use the typical UISCrollView technique) and whenever I zoom in in one of them will result to zoom in the other as well, in the same point and with the same zoom.
Here's an explicative video: https://youtu.be/Ft-Dt3fx3z8?t=29s
Do you know any solution?
Thanks
I will answer my own question, first solution zooming async without scrolling
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if (scrollView == _scrollView1)
return self.imageView1;
else
return self.imageView2;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
if (scrollView == _scrollView1){
[_scrollView2 setZoomScale:scale animated:YES];
[_scrollView2 setContentOffset:_scrollView1.contentOffset animated:YES];
}
else if (scrollView == _scrollView2){
[_scrollView1 setZoomScale:scale animated:YES];
[_scrollView1 setContentOffset:_scrollView2.contentOffset animated:YES];
}
}
Second, better solution with real time zooming and scrolling (viewForZoomingInScrollView is still needed)
- (void)matchScrollView:(UIScrollView *)first toScrollView:(UIScrollView *)second {
CGPoint offset = first.contentOffset;
offset.x = second.contentOffset.x;
offset.y = second.contentOffset.y;
[first setContentOffset:offset];
[first setZoomScale:second.zoomScale];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if([scrollView isEqual:_scrollView1]) {
[self matchScrollView:_scrollView2 toScrollView:_scrollView1];
} else {
[self matchScrollView:_scrollView1 toScrollView:_scrollView2];
}
}
Related
I have a custom view replacing the keyboard and I'm trying to get it to scroll offscreen when the user scrolls down.
My original scrollViewDelegate methods worked EXCEPT there was a delay between user scrolling and view animation because I was using scrollViewDidEndDecelerating and it took about 1 second for this to be called after the user started scrolling.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isUserScrolling = YES;
// Save to tell if scrolling up or down
initialContentOffset = scrollView.contentOffset.y;
previousContentDelta = 0.f;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.isUserScrolling = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.isUserScrolling = NO;
// If scrolled down
CGFloat prevDelta = previousContentDelta;
CGFloat delta = scrollView.contentOffset.y - initialContentOffset;
if (delta > 0.f && prevDelta <= 0.f) {
} else if (delta < 0.f && prevDelta >= 0.f) {
[self animateKeyBoardSpace:[self rectForKeyboardSpace:NO] curve:UIViewAnimationCurveEaseInOut duration:0.25];
}
previousContentDelta = delta;
}
So I am trying to now check for downward scrolling in scrollViewDidScroll and call animateKeyBoardSpace there like so:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// ScrollDirection scrollDirection;
if (lastContentOffset > scrollView.contentOffset.y) {
[self animateKeyBoardSpace:[self rectForKeyboardSpace:NO] curve:UIViewAnimationCurveEaseInOut duration:0.25];
}
else if (lastContentOffset < scrollView.contentOffset.y) {
}
lastContentOffset = scrollView.contentOffset.x;
}
HOWEVER, scrollViewDidScroll isn't even getting called. It's in the same tableViewController, the delegate is set, and the other delegate methods are getting called.
I was working in the superclass and I had a stub for scrollViewDidScroll in the subclass from some time ago. This was capturing the delegate call.
I just had to delete the subclass stub.
I'm using standard UIPageViewController here and I need to track x coordinate in order to change the alpha of the image accordingly. I try to find the UIScrollView in UIPageViewController like this and it works:
for (UIView *view in self.pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[(UIScrollView *)view setDelegate:self];
}
}
But when I do this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
MTWPageContentViewController *pageContentVC = [self viewControllerAtIndex:_mainPageControl.currentPage];
NSLog(#"%f", pageContentVC.view.frame.origin.x);
}
NSLog gives me 0.000 which obviously means that I'm doing something wrong.
pageContentVC exists and it is the view controller visible at the moment I scroll.
So, tell me how do I track x coordinate of UIPageViewController's subview while scrolling?
You can grab the gesture recognisers from the UIPageViewController and add yourself as a target/action to get information about the pan.
for (UIGestureRecognizer *gestureRecognizer in self.pageViewController.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
[gestureRecognizer addTarget:self action:#selector(panned:)];
}
}
I'm trying to animate a radar image on an MKMapView. I have separate images for different times, and I have successfully set an image as an overlay on top of the MKMapView. My problem is when I try to show these different images in a sequence one after each other. I tried a method of adding and removing overlays, but the system does not handle it well at all, with flickering of overlays, and some overlays not being removed, and overall, its not worth it.
Could anyone help me find a way to show multiple images (like an animated gif) on top of a MapView in a way that is smooth and fast?
Here's my code to overlay the image:
- (id)initWithImageData:(NSData*)imageData withLowerLeftCoordinate:(CLLocationCoordinate2D)lowerLeftCoordinate withUpperRightCoordinate:(CLLocationCoordinate2D)upperRightCoordinate {
self.radarData = imageData;
MKMapPoint lowerLeft = MKMapPointForCoordinate(lowerLeftCoordinate);
MKMapPoint upperRight = MKMapPointForCoordinate(upperRightCoordinate);
mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);
return self;
}
- (CLLocationCoordinate2D)coordinate
{
return MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMidX(mapRect), MKMapRectGetMidY(mapRect)));
}
- (MKMapRect)boundingMapRect
{
return mapRect;
}
and here's how I'm setting it up on the MapView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)ctx
{
MapOverlay *mapOverlay = (MapOverlay *)self.overlay;
image = [[UIImage alloc] initWithData:mapOverlay.radarData];
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
#try {
UIGraphicsBeginImageContext(image.size);
UIGraphicsPushContext(ctx);
[image drawInRect:theRect blendMode:kCGBlendModeNormal alpha:0.5];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
}
#catch (NSException *exception) {
NSLog(#"Caught an exception while drawing radar on map - %#",[exception description]);
}
#finally {
}
}
and here's where I'm adding the overlay:
- (void)mapRadar {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.mapOverlay = [[MapOverlay alloc] initWithImageData:appDelegate.image withLowerLeftCoordinate:CLLocationCoordinate2DMake(appDelegate.south, appDelegate.west) withUpperRightCoordinate:CLLocationCoordinate2DMake(appDelegate.north, appDelegate.east)];
[self.mapView addOverlay:self.mapOverlay];
[self.mapView setNeedsDisplay];
self.mapView.showsUserLocation = YES;
MKMapPoint lowerLeft2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.south2, appDelegate.west2) );
MKMapPoint upperRight2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.north2, appDelegate.east2));
MKMapRect localMapRect = MKMapRectMake(lowerLeft2.x, upperRight2.y, upperRight2.x - lowerLeft2.x, lowerLeft2.y - upperRight2.y);
[self.mapView setVisibleMapRect:localMapRect animated:YES];
}
#pragma Mark - MKOverlayDelgateMethods
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay{
if([overlay isKindOfClass:[MapOverlay class]]) {
MapOverlay *mapOverlay = overlay;
self.mapOverlayView = [[MapOverlayView alloc] initWithOverlay:mapOverlay];
}
return self.mapOverlayView;
}
Does anyone have an idea or a solution where I can show a sequence of images on an MKMapView smoothly? At this point, I'm desperate for solutions and can't find it anywhere else, so anything would help me, thanks!
Using Lefteris' suggestion, you can just use UIImageView with animationImages. The animation should be smooth provided your overlay images are properly sized for the screen.
To pass touches through to the map view below, you can create a new UIView subclass with a (weak) reference to the map view. Override the touch event methods (touchesBegan:withEvent:, touchesMoved:withEvent:, etc) to forward to the map view, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.mapView touchesBegan:touches withEvent:event];
}
This way the user can still interact with the view below.
You may run into problems when the user tries to zoom, as it will be difficult if not impossible to scale all your animation frames in real-time. You should look into using CATiledLayer instead.
I am working with two subviews. Each will be unique and have it's own "action".
Subview 1 = User can drag around the view, rotate, and zoom it
Subview 2 = When user moves finger across their screen an image is added at each point their finger touches.
I have both of these completed by using UIPanGestureRecognizer. My question is, how can I separate these two actions? I want to be able to add one subview, do what is required, and then when I add the other subview, prevent the previous actions from occurring.
Here is what I have tried, this is done in my panGesture method:
for (UIView * subview in imageView.subviews)
{
if ([subview isKindOfClass:[UIImageView class]])
{
if (subview == _aImageView)
{
CGPoint translation = [panRecognizer translationInView:self.view];
CGPoint imageViewPosition = _aImageView.center;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
_aImageView.center = imageViewPosition;
[panRecognizer setTranslation:CGPointZero inView:self.view];
}
else if (subview == _bImageView)
{
currentTouch = [panRecognizer locationInView:self.view];
CGFloat distance = [self distanceFromPoint:currentTouch ToPoint:prev_touchPoint];
accumulatedDistance += distance;
CGFloat fixedDistance = 60;
if ([self distanceFromPoint:currentTouch ToPoint:prev_touchPoint] > fixedDistance)
{
[self addbImage];
prev_touchPoint = currentTouch;
}
}
}
}
If you want different gesture recognition in two different views, put separate recognizers on each view.
Usually, you want to have your view controller own and manage gesture recognizers, e.g.
- (void)viewDidLoad {
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
self.panGesture.delegate = self;
[self.viewX addGestureRecognizer:self.panGesture];
// repeat with other recognisers...
}
Note that setting your controller as delegate of the gestureRecognizer is important: this enables you to handle the following delegate method from the view controller (which was the main question):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// handle your logic, which gestureRecognizer should proceed...
return NO;
}
The handler method is the same is this example, but you can set up your own handlers as you like:
- (void)handleGesture:(UIGestureRecognizer*)gestureRecognizer {
// handle gesture (usually sorted by state), e.g.
// if(gesture.state == UIGestureRecognizerStateEnded) { ... }
}
I have a splitViewController. This is in Detail VC
-(void)viewDidLoad
{
self.masterIsVisible = YES;
//a botton in navigation bar to hide or show the master view.
[button addTarget:self action:#selector(showOrHideMasterView)
forControlEventsTouchUpInside]
//gesture control to swipe right or left to slide master view in and out.
[swiperight addTarget:self action:#selector(showMasterView)];
[swipLeft addTarget:self action:#selector(hideMasterView)];
}
-(void)showOrHideMasterView
{
if (self.masterIsVisible)
[self hidemasterView]; self.masterIsVisible = NO;
else
[self showMasterView]; self.masterIsVisible = YES;
}
-(void)hideMasterView
{
//hides master view by substracting masterview's width from its origin.x
}
-(void)showMasterView
{
//shows master View by adding masterview's width to its origin.x
}
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController: (UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return NO;
}
Everything almost works as intended.
Problem: In one orientation && master is NOT visible.. then device changes orientation.. the master View instead of sliding off the screen pushed the detail view the other way. I know thats because the flag now is set as masterIsVisible = NO instead of YES. What can I do to change the flag to YES on device rotation. looks trivial but cant seem to figure out.
I tried registering for devicechnagenotification in UIDevice but that did not work. The BOOL is YES in any Orientation. The apple example uses this but looks like thats not the right approach here.
Ok, I finally figured out to set the flag correctly for orientation change. I added the following method
-(void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
self.masterIsVisible = NO;
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
self.masterIsVisible = YES;
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
self.masterIsVisible = YES;
else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
self.masterIsVisible = NO;
}