Как рассчитать правильное происхождение CGRect для масштабированного подпредставления UIView? - PullRequest
0 голосов
/ 28 сентября 2018

Мне нужно вычислить видимый CGRect подпредставления UIView в координатах исходного представления.У меня это работает, если масштаб равен 1, но если один из суперпредставлений или само представление масштабируется (сжимается), видимый источник CGRect слегка смещается.

Это работает, когда масштаб видовравно 1 или представление является подпредставлением корневого представления:

// return the part of the passed view that is visible
// TODO:  figure out why result origin is wrong for scaled subviews
//
- (CGRect)getVisibleRect:(UIView *)view {
    // get the root view controller (and it's view is vc.view)
    UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;

    // get the view's frame in the root view's coordinate system
    CGRect frame = [vc.view convertRect:view.frame fromView:view.superview];

    // get the intersection of the root view bounds and the passed view frame
    CGRect intersection = CGRectIntersection(vc.view.bounds, frame);

    // adjust the intersection coordinates thru any nested views
    UIView *loopView = view;
    do {
        intersection = [loopView convertRect:intersection fromView:loopView.superview];

        loopView = loopView.superview;
    } while (loopView != vc.view);

    return intersection; // may be same as the original view frame
}

Когда масштабное отображение подпредставления, размер результирующего представления является правильным, но источник смещается на небольшое количество.Похоже, что convertRect не рассчитывает исходную точку правильно для масштабированных подпредставлений.

Я попытался отрегулировать исходную точку относительно шкалы преобразования X / Y, но не смог получить правильные вычисления.Может быть, кто-то может помочь?

Чтобы сэкономить время, вот полный тест ViewController.m, где на видимой части видов рисуется прямоугольник с X - просто создайте кнопку сброса на Main.storyboardи подключите его к методу reset:

//
//  ViewController.m
//  VisibleViewDemo
//
//  Copyright © 2018 ByteSlinger. All rights reserved.
//

#import "ViewController.h"

CG_INLINE void drawLine(UIView *view,CGPoint point1,CGPoint point2, UIColor *color, NSString *layerName) {
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:point1];
    [path addLineToPoint:point2];

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = [path CGPath];
    shapeLayer.strokeColor = color.CGColor;
    shapeLayer.lineWidth = 2.0;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.name = layerName;

    [view.layer addSublayer:shapeLayer];
}

CG_INLINE void removeShapeLayers(UIView *view,NSString *layerName) {
    if (view.layer.sublayers.count > 0) {
        for (CALayer *layer in [view.layer.sublayers copy]) {
            if ([layer.name isEqualToString:layerName]) {
                [layer removeFromSuperlayer];
            }
        }
    }
}

CG_INLINE void drawXBox(UIView *view, CGRect rect,UIColor *color) {
    NSString *layerName = @"xbox";

    removeShapeLayers(view, layerName);

    CGPoint topLeft = CGPointMake(rect.origin.x,rect.origin.y);
    CGPoint topRight = CGPointMake(rect.origin.x + rect.size.width,rect.origin.y);
    CGPoint bottomLeft = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height);
    CGPoint bottomRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);

    drawLine(view,topLeft,topRight,color,layerName);
    drawLine(view,topRight,bottomRight,color,layerName);
    drawLine(view,topLeft,bottomLeft,color,layerName);
    drawLine(view,bottomLeft,bottomRight,color,layerName);
    drawLine(view,topLeft,bottomRight,color,layerName);
    drawLine(view,topRight,bottomLeft,color,layerName);
}

@interface ViewController ()

@end

@implementation ViewController

UIView *view1;
UIView *view2;
UIView *view3;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    CGFloat width = [UIScreen mainScreen].bounds.size.width / 2;
    CGFloat height = [UIScreen mainScreen].bounds.size.height / 4;

    view1 = [[UIView alloc] initWithFrame:CGRectMake(width / 2, height / 2, width, height)];
    view1.backgroundColor = UIColor.yellowColor;
    [self.view addSubview:view1];
    [self addGestures:view1];

    view2 = [[UIView alloc] initWithFrame:CGRectMake(width / 2, height / 2 + height + 16, width, height)];
    view2.backgroundColor = UIColor.greenColor;
    [self.view addSubview:view2];
    [self addGestures:view2];

    view3 = [[UIView alloc] initWithFrame:CGRectMake(10, 10, width / 2, height / 2)];
    view3.backgroundColor = [UIColor.blueColor colorWithAlphaComponent:0.5];
    [view1 addSubview:view3];       // this one will behave differently
    [self addGestures:view3];
}
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [self checkOnScreen:view1];
    [self checkOnScreen:view2];
    [self checkOnScreen:view3];
}

- (IBAction)reset:(id)sender {
    view1.transform = CGAffineTransformIdentity;
    view2.transform = CGAffineTransformIdentity;
    view3.transform = CGAffineTransformIdentity;

    [self.view setNeedsLayout];
}

- (void)addGestures:(UIView *)view {
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc]
                                                    initWithTarget:self action:@selector(handlePan:)];
    [view addGestureRecognizer:panGestureRecognizer];

    UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc]
                                                        initWithTarget:self action:@selector(handlePinch:)];
    [view addGestureRecognizer:pinchGestureRecognizer];
}

// return the part of the passed view that is visible
- (CGRect)getVisibleRect:(UIView *)view {
    // get the root view controller (and it's view is vc.view)
    UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;

    // get the view's frame in the root view's coordinate system
    CGRect frame = [vc.view convertRect:view.frame fromView:view.superview];

    // get the intersection of the root view bounds and the passed view frame
    CGRect intersection = CGRectIntersection(vc.view.bounds, frame);

    // adjust the intersection coordinates thru any nested views
    UIView *loopView = view;
    do {
        intersection = [loopView convertRect:intersection fromView:loopView.superview];

        loopView = loopView.superview;
    } while (loopView != vc.view);

    return intersection; // may be same as the original view
}

- (void)checkOnScreen:(UIView *)view {
    CGRect visibleRect = [self getVisibleRect:view];

    if (CGRectEqualToRect(visibleRect, CGRectNull)) {
        visibleRect = CGRectZero;
    }

    drawXBox(view,visibleRect,UIColor.blackColor);
}

//
// Pinch (resize) an image on the ViewController View
//
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer {
    static CGAffineTransform initialTransform;

    if (recognizer.state == UIGestureRecognizerStateBegan) {
        [self.view bringSubviewToFront:recognizer.view];

        initialTransform = recognizer.view.transform;
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
    } else {
        recognizer.view.transform = CGAffineTransformScale(initialTransform,recognizer.scale,recognizer.scale);

        [self checkOnScreen:recognizer.view];

        [self.view setNeedsLayout]; // update subviews
    }
}

- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
    static CGAffineTransform initialTransform;

    if (recognizer.state == UIGestureRecognizerStateBegan) {
        [self.view bringSubviewToFront:recognizer.view];

        initialTransform = recognizer.view.transform;
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
    } else {
        //get the translation amount in x,y
        CGPoint translation = [recognizer translationInView:recognizer.view];

        recognizer.view.transform = CGAffineTransformTranslate(initialTransform,translation.x,translation.y);

        [self checkOnScreen:recognizer.view];

        [self.view setNeedsLayout]; // update subviews
    }
}
@end

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Спасибо @Michael за то, что он приложил столько усилий в своем ответе.Это не решило проблему, но заставило меня подумать еще и попробовать другие вещи.

И вуаля, я попробовал кое-что, что, я уверен, я делал раньше, но на этот раз я начал с моей последнейкод.Оказывается, простое решение сделало свое дело.Встроенные UIView convertRect:fromView и convertRect:toView работали, как и ожидалось, при совместном использовании.

Я приношу извинения всем, кто потратил на это время.Я унижен своей глупостью и тем, сколько времени я потратил на это.Должно быть, я где-то допустил ошибку, когда пытался сделать это раньше, потому что это не сработало.Но теперь это работает очень хорошо:

// return the part of the passed view that is visible
- (CGRect)getVisibleRect:(UIView *)view {
    // get the root view controller (and it's view is vc.view)
    UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;

    // get the view's frame in the root view's coordinate system
    CGRect rootRect = [vc.view convertRect:view.frame fromView:view.superview];

    // get the intersection of the root view bounds and the passed view frame
    CGRect rootVisible = CGRectIntersection(vc.view.bounds, rootRect);

    // convert the rect back to the initial view's coordinate system
    CGRect visible = [view convertRect:rootVisible fromView:vc.view];

    return visible; // may be same as the original view frame
}

Если кто-то использует Viewcontroller.m из моего вопроса, просто замените метод getVisibleRect на этот, и он будет работать очень хорошо.

ПРИМЕЧАНИЕ:Я попытался повернуть вид, и видимый прямоугольник тоже повернулся, потому что я отобразил его на самом виде.Я думаю, что я мог бы повернуть вспять любое вращение вида на слоях фигур, но это на другой день!

0 голосов
/ 29 сентября 2018

Таким образом, вам нужно знать реально видимое frame представление, которое каким-то образом получено из bounds + center + transform, и вычислять все остальное из этого, вместо обычного значения frame.Это означает, что вам также придется воссоздать convertRect:fromView:, чтобы основываться на этом.Я всегда обходил проблему, используя transform только для коротких анимаций, где такие вычисления не нужны.Думая о кодировании такого -getVisibleRect: метода, я хочу убежать от крика;)

Что такое frame?

Свойство frame получено из center и bounds.

Пример:

  • center is (60,50)
  • bounds is (0,0,100,100)
  • => frame is (10,0,100,100)

Теперь вы измените frame на (10,20,100,100).Поскольку размер представления не изменился, это приводит только к изменению center.Новый center теперь (60,70).

Как насчет transform?

Скажем, теперь вы трансформируете представление, увеличив его до 50%.

=> представление теперь имеет половину размера, чем раньше, при этом сохраняя тот же центр.Похоже, новый кадр (35,45,50,50).Однако реальный результат:

  • center по-прежнему (60,50): ожидается
  • bounds по-прежнему (0,0,100,100): этого тоже следует ожидать
  • frame по-прежнему (10,20,100,100): это несколько нелогично

frame является вычисляемым свойством, и его не волнует текущееtransform.Это означает, что значение frame не имеет смысла, когда transform не является тождественным преобразованием.Это даже задокументированное поведение .В этом случае Apple называет значение frame неопределенным.

Последствия

Это имеет дополнительные последствия, заключающиеся в том, что такие методы, как convertRect:fromView:, не работают должным образом, когда нет-стандартные преобразования участвуют.Это потому, что все эти методы основаны на frame или bounds представлений, и они разрушаются, как только происходят преобразования.

Что можно сделать?

Скажите, что у вас естьтри вида:

  • view1 (без преобразования)
    • view2 (масштабное преобразование 50%)
      • view3 (без преобразования)

и вы хотите узнать координаты view3 с точки зрения view1.

С точки зрения view2 view3 имеет рамку view3.frame.Легко.С точки зрения view1, view2 не имеет рамки view2.frame, но видимая рамка представляет собой прямоугольник с размером view2.bounds/2 и центром view2.center.

Чтобы получить это право, вам нужна базовая линейная алгебра(с умножением матрицы).(И не забудьте anchorPoint ..)

Надеюсь, это поможет ..

Что можно сделать по-настоящему?

В вашемВопрос, который вы сказали, что есть смещение.Может быть, вы можете просто посчитать ошибку сейчас?Ошибка должна быть что-то вроде 0,5 * (1-шкала) * (bounds.size).Если вы можете рассчитать ошибку, вы можете вычесть ее и назвать день:)

...