Jak dostosować punkt zakotwiczenia CALayer, gdy używany jest układ automatyczny?


161

Uwaga : od czasu zadania tego pytania sytuacja się zmieniła; zobacz tutaj, aby uzyskać dobry przegląd.


Przed automatycznym układem można było zmienić punkt zakotwiczenia warstwy widoku bez przesuwania widoku, przechowując ramkę, ustawiając punkt zakotwiczenia i przywracając ramkę.

W świecie automatycznego układu nie ustawiamy już ramek, ale wydaje się, że ograniczenia nie wystarczają do dostosowania pozycji widoku z powrotem do miejsca, w którym chcemy. Możesz zhakować ograniczenia, aby zmienić położenie widoku, ale przy obracaniu lub innych zdarzeniach zmiany rozmiaru stają się one ponownie nieważne.

Następujący genialny pomysł nie działa, ponieważ tworzy „nieprawidłowe połączenie atrybutów układu (lewy i szerokość)”:

layerView.layer.anchorPoint = CGPointMake(1.0, 0.5);
// Some other size-related constraints here which all work fine...
[self.view addConstraint:
    [NSLayoutConstraint constraintWithItem:layerView
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:layerView 
                                 attribute:NSLayoutAttributeWidth 
                                multiplier:0.5 
                                  constant:20.0]];

Moim zamiarem było ustawienie lewej krawędzi layerViewwidoku z dostosowanym punktem zakotwiczenia na połowę jego szerokości plus 20 (odległość, którą chcę wstawić od lewej krawędzi nadzoru).

Czy można zmienić punkt zakotwiczenia bez zmiany położenia widoku w widoku z układem automatycznym? Czy muszę używać wartości zakodowanych na stałe i edytować ograniczenie przy każdym obrocie? Mam nadzieję, że nie.

Muszę zmienić punkt zakotwiczenia, aby po zastosowaniu transformacji do widoku uzyskać prawidłowy efekt wizualny.


Biorąc pod uwagę to, co tutaj masz, wydaje się, że i tak skończysz z niejednoznacznymi układami, nawet jeśli kod powyżej działa. Skąd layerViewznasz jego szerokość? Czy przykleja swoją prawą stronę do czegoś innego?
John Estropia,

Jest to omówione w //Size-related constraints that work fine- szerokość i wysokość widoku warstwy pochodzi z superwiewu.
jrturton

Odpowiedzi:


361

[EDYCJA: Ostrzeżenie: cała następująca dyskusja zostanie prawdopodobnie przestarzała lub przynajmniej mocno złagodzona przez iOS 8, który może już nie popełniać błędu uruchamiania układu w momencie zastosowania transformacji widoku].

Autoukład a widok przekształceń

Autolayout nie działa dobrze z transformacjami widoku. Powód, o ile mogę stwierdzić, jest taki, że nie należy mieszać w ramkę widoku, który ma transformację (inną niż domyślna transformacja tożsamości) - ale właśnie to robi autoukład. Sposób działania autoukładu polega na tym, że w layoutSubviewsśrodowisku wykonawczym przechodzi przez wszystkie ograniczenia i odpowiednio ustawia klatki wszystkich widoków.

Innymi słowy, ograniczenia nie są magiczne; to tylko lista rzeczy do zrobienia. layoutSubviewsto miejsce, w którym lista rzeczy do zrobienia jest wykonywana. I robi to poprzez ustawienie ramek.

Nie mogę pomóc w traktowaniu tego jako błędu. Jeśli zastosuję tę transformację do widoku:

v.transform = CGAffineTransformMakeScale(0.5,0.5);

Spodziewam się, że widok pojawi się ze środkiem w tym samym miejscu co poprzednio i będzie o połowę mniejszy. Ale w zależności od ograniczeń może to wcale nie być to, co widzę.

[Właściwie jest tu druga niespodzianka: zastosowanie transformacji do widoku natychmiast uruchamia układ. Wydaje mi się, że to kolejny błąd. A może jest to sedno pierwszego błędu. To, czego bym się spodziewał, to móc uciec z transformacją przynajmniej do czasu rozmieszczenia, np. Urządzenie jest obrócone - tak jak mogę uciec z animacją klatki do czasu rozmieszczenia. Ale w rzeczywistości czas na przygotowanie jest natychmiastowy, co wydaje się po prostu niewłaściwe.]

Rozwiązanie 1: Brak ograniczeń

Jedno z obecnych rozwiązań polega na tym, że jeśli mam zamiar zastosować półtrwałą transformację do widoku (a nie tylko tymczasowo nim poruszać), usunąć wszystkie ograniczenia, które na niego wpływają. Niestety, zazwyczaj powoduje to zniknięcie widoku z ekranu, ponieważ nadal ma miejsce autoukład, a teraz nie ma żadnych ograniczeń, aby powiedzieć nam, gdzie umieścić widok. Więc oprócz usunięcia ograniczeń, ustawiłem widok na translatesAutoresizingMaskIntoConstraintsTAK. Widok działa teraz w stary sposób, bez wpływu autoukładu. (Oczywiście ma na to wpływ autoukład, ale niejawne ograniczenia dotyczące autorezyzacji maski powodują, że jej zachowanie jest takie samo, jak przed autoukładem).

Rozwiązanie 2: Używaj tylko odpowiednich ograniczeń

Jeśli wydaje się to nieco drastyczne, innym rozwiązaniem jest ustawienie ograniczeń, aby działały poprawnie z zamierzoną transformacją. Jeśli rozmiar widoku jest określany wyłącznie na podstawie swojej wewnętrznej stałej szerokości i wysokości i jest umieszczony wyłącznie według jego środka, na przykład moja transformacja skali będzie działać zgodnie z oczekiwaniami. W tym kodzie usuwam istniejące ograniczenia z widoku podrzędnego ( otherView) i zastępuję je tymi czterema ograniczeniami, nadając mu stałą szerokość i wysokość oraz przypinając go wyłącznie po środku. Po tym moja transformacja skali działa:

NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
    if (con.firstItem == self.otherView || con.secondItem == self.otherView)
        [cons addObject:con];

[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];
[self.view addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:self.otherView.center.y]];
[self.otherView addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.width]];
[self.otherView addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.height]];

Wynik jest taki, że jeśli nie masz żadnych ograniczeń, które wpływają na ramkę widoku, autoukład nie dotknie ramki widoku - co jest właśnie tym, czego szukasz, gdy zaangażowana jest transformacja.

Rozwiązanie 3: Użyj widoku podrzędnego

Problem z obydwoma powyższymi rozwiązaniami polega na tym, że tracimy korzyści wynikające z ograniczeń pozycjonowania naszego punktu widzenia. Oto rozwiązanie, które to rozwiązuje. Zacznij od niewidocznego widoku, którego zadaniem jest wyłącznie pełnienie roli gospodarza, i użyj ograniczeń, aby go ustawić. Wewnątrz umieść prawdziwy widok jako podgląd podrzędny. Użyj ograniczeń, aby umieścić podgląd podrzędny w widoku nadrzędnym, ale ogranicz te ograniczenia do ograniczeń, które nie będą walczyć, gdy zastosujemy transformację.

Oto ilustracja:

wprowadź opis obrazu tutaj

Widok biały to widok hosta; powinieneś udawać, że jest przezroczysty i przez to niewidoczny. Widok czerwony to jego widok podrzędny, umieszczony przez przypięcie jego środka do środka widoku nadrzędnego. Teraz możemy bez problemu skalować i obracać czerwony widok wokół jego środka, a ilustracja rzeczywiście pokazuje, że to zrobiliśmy:

self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);

W międzyczasie ograniczenia widoku hosta utrzymują go we właściwym miejscu, gdy obracamy urządzenie.

Rozwiązanie 4: Zamiast tego użyj przekształceń warstw

Zamiast przekształceń widoku należy używać przekształceń warstw, które nie powodują wyzwalania układu, a tym samym nie powodują natychmiastowego konfliktu z ograniczeniami.

Na przykład ta prosta animacja widoku „pulsującego” może się zepsuć podczas automatycznego układania:

[UIView animateWithDuration:0.3 delay:0
                    options:UIViewAnimationOptionAutoreverse
                 animations:^{
    v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
    v.transform = CGAffineTransformIdentity;
}];

Mimo że ostatecznie nie nastąpiła zmiana rozmiaru widoku, wystarczy ustawić jego transformukład przyczyn, a ograniczenia mogą sprawić, że widok przeskoczy. (Czy to wygląda na błąd, czy co?) Ale jeśli zrobimy to samo z Core Animation (używając CABasicAnimation i stosując animację do warstwy widoku), układ nie występuje i działa dobrze:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[v.layer addAnimation:ba forKey:nil];

3
To nie transformacja sprawia mi problem, po prostu ustawia nowy punkt zakotwiczenia. Spróbuję jednak usunąć ograniczenia i wypróbować maskę autorezowania.
jrturton,

1
Coś tu nie rozumiem. Rozwiązanie 4: Kiedy transformacja jest stosowana do warstwy podkładowej, która transformacja wpływa również na autolayout, ponieważ właściwość transform widoku odnosi się do transformacji warstwy podkładowej
Luca Bartoletti

1
@Andy wręcz przeciwnie, na iOS 8 problem całkowicie zniknął i cała moja odpowiedź jest niepotrzebna
mat.

1
@Sunkas Odpowiedziałem w twoim problemie.
Ben Sinclair

2
W systemie iOS 8 transformacja warstwy wyzwala również automatyczny układ
CarmeloS

41

Miałem podobny Isuue i właśnie usłyszałem Back od zespołu Autolayout w Apple. Sugerują użycie podejścia Container View Approach, które sugeruje mat, ale tworzą podklasę UIView, aby nadpisać układ Podglądy i zastosować tam niestandardowy układ Kod - Działa jak urok

Plik nagłówkowy wygląda tak, że możesz połączyć wybrany widok podrzędny bezpośrednio z programu Interface Builder

#import <UIKit/UIKit.h>

@interface BugFixContainerView : UIView
@property(nonatomic,strong) IBOutlet UIImageView *knobImageView;
@end

a plik m stosuje specjalny kod w ten sposób:

#import "BugFixContainerView.h"

@implementation BugFixContainerView
- (void)layoutSubviews
{
    static CGPoint fixCenter = {0};
    [super layoutSubviews];
    if (CGPointEqualToPoint(fixCenter, CGPointZero)) {
        fixCenter = [self.knobImageView center];
    } else {
        self.knobImageView.center = fixCenter;
    }
}
@end

Jak widać, przy pierwszym wywołaniu chwyta środkowy punkt widoku i ponownie wykorzystuje tę pozycję w kolejnych wywołaniach, aby odpowiednio umieścić widok. To nadpisuje kod autoukładu w tym sensie, że ma to miejsce po [super layoutSubviews]; który zawiera kod autoukładu.

W ten sposób nie ma już potrzeby unikania automatycznego układu, ale możesz utworzyć własny układ, gdy domyślne zachowania nie są już odpowiednie. Oczywiście możesz zastosować znacznie bardziej skomplikowane rzeczy niż to, co jest w tym przykładzie, ale to było wszystko, czego potrzebowałem, ponieważ moja aplikacja może używać tylko trybu portretu.


5
Dziękuję za wysłanie tego. Odkąd napisałem swoją odpowiedź, zdecydowanie zrozumiałem wartość zastępowania layoutSubviews. Chciałbym tylko dodać, że tutaj Apple po prostu zmusza cię do robienia tego, co myślę, że powinni byli zrobić (w ich implementacji Autolayout) przez cały czas.
mat.

Masz rację, ale myślę, że to nie jest tak naprawdę związane z samym problemem. Ponieważ dostarczyli powyższy kod, jestem z tego zadowolony!
sensslen

4
To smutne i absurdalne, że zespół autoukładu sam zaleca utworzenie niestandardowej podklasy kontenera tylko po to, aby obejść sposób, w jaki autoukład łamie długotrwałe i intuicyjne zachowanie w systemie widoku.
glony

8

Znajduję prosty sposób. Działa na iOS 8 i iOS 9.

Podobnie jak w przypadku dopasowania anchorPoint do układu opartego na ramkach:

let oldFrame = layerView.frame
layerView.layer.anchorPoint = newAnchorPoint
layerView.frame = oldFrame

Kiedy dostosowujesz zakotwiczenie widoku za pomocą automatycznego układu, robisz to samo, ale w sposób ograniczony. Gdy punkt anchorPoint zmieni się z (0,5, 0,5) na (1, 0,5), layerView przesunie się w lewo o połowę długości szerokości widoku, więc musisz to skompensować.

Nie rozumiem ograniczenia w pytaniu, więc załóżmy, że dodajesz ograniczenie centerX względem superView centerX ze stałą: layerView.centerX = superView.centerX + stała

layerView.layer.anchorPoint = CGPoint(1, 0.5)
let centerXConstraint = .....
centerXConstraint.constant = centerXConstraint.constant + layerView.bounds.size.width/2

Szklanka najlepszego piwa dla ciebie przyjacielu. To jest takie dobre obejście: D
Błażej

3

Jeśli używasz automatycznego układu, nie widzę, jak ręczne ustawienie pozycji będzie służyć w dłuższej perspektywie, ponieważ w końcu automatyczny układ przeskoczy wartość pozycji ustawioną przez Ciebie podczas obliczania własnego układu.

Potrzebne jest raczej zmodyfikowanie samych ograniczeń układu, aby skompensować zmiany wywołane przez ustawienie anchorPoint. Następująca funkcja robi to dla nieprzekształconych widoków.

/**
  Set the anchorPoint of view without changing is perceived position.

 @param view view whose anchorPoint we will mutate
 @param anchorPoint new anchorPoint of the view in unit coords (e.g., {0.5,1.0})
 @param xConstraint an NSLayoutConstraint whose constant property adjust's view x.center
 @param yConstraint an NSLayoutConstraint whose constant property adjust's view y.center

  As multiple constraints can contribute to determining a view's center, the user of this
 function must specify which constraint they want modified in order to compensate for the
 modification in anchorPoint
 */
void SetViewAnchorPointMotionlesslyUpdatingConstraints(UIView * view,CGPoint anchorPoint,
                                                       NSLayoutConstraint * xConstraint,
                                                       NSLayoutConstraint * yConstraint)
{
  // assert: old and new anchorPoint are in view's unit coords
  CGPoint const oldAnchorPoint = view.layer.anchorPoint;
  CGPoint const newAnchorPoint = anchorPoint;

  // Calculate anchorPoints in view's absolute coords
  CGPoint const oldPoint = CGPointMake(view.bounds.size.width * oldAnchorPoint.x,
                                 view.bounds.size.height * oldAnchorPoint.y);
  CGPoint const newPoint = CGPointMake(view.bounds.size.width * newAnchorPoint.x,
                                 view.bounds.size.height * newAnchorPoint.y);

  // Calculate the delta between the anchorPoints
  CGPoint const delta = CGPointMake(newPoint.x-oldPoint.x, newPoint.y-oldPoint.y);

  // get the x & y constraints constants which were contributing to the current
  // view's position, and whose constant properties we will tweak to adjust its position
  CGFloat const oldXConstraintConstant = xConstraint.constant;
  CGFloat const oldYConstraintConstant = yConstraint.constant;

  // calculate new values for the x & y constraints, from the delta in anchorPoint
  // when autolayout recalculates the layout from the modified constraints,
  // it will set a new view.center that compensates for the affect of the anchorPoint
  CGFloat const newXConstraintConstant = oldXConstraintConstant + delta.x;
  CGFloat const newYConstraintConstant = oldYConstraintConstant + delta.y;

  view.layer.anchorPoint = newAnchorPoint;
  xConstraint.constant = newXConstraintConstant;
  yConstraint.constant = newYConstraintConstant;
  [view setNeedsLayout];
}

Przyznaję, że to prawdopodobnie nie wszystko, na co liczyłeś, ponieważ zwykle jedynym powodem, dla którego chcesz zmodyfikować anchorPoint, jest ustawienie transformacji. Wymagałoby to bardziej złożonej funkcji, która aktualizuje ograniczenia układu, aby odzwierciedlić wszystkie zmiany ramki, które mogą być spowodowane przez samą właściwość transform. Jest to trudne, ponieważ transformacje mogą wiele zmienić w ramce. Skalowanie lub transformacja obrotu spowodowałaby zwiększenie rozmiaru ramki, więc musielibyśmy zaktualizować wszelkie ograniczenia szerokości lub wysokości itp.

Jeśli używasz transformacji tylko do tymczasowej animacji, to, co powyżej, może wystarczyć, ponieważ nie wierzę, że automatyczny układ uniemożliwi animacji w locie przedstawianie obrazów, które reprezentują czysto przejściowe naruszenia ograniczeń.


Niestety używam transformacji. Ustawienie pozycji jest w porządku, ponieważ metoda jest wywoływana za każdym razem, gdy widok jest rozkładany, więc nie jest blokowany. Myślę, że nadpisanie layoutRect może być lepszym rozwiązaniem (wspomniane w jednej z twoich wcześniejszych odpowiedzi), ale jeszcze go nie wypróbowałem.
jrturton

Przy okazji, oto stanowisko testowe, którego używałem. Byłoby wspaniale, gdyby ktoś zdobione to: github.com/algal/AutolayoutAnchorPointTestRig
glonów

To powinna być akceptowana odpowiedź. Odpowiedź @mata nie dotyczy nawet punktu kontrolnego.
Iulian Onofrei

3

tl: dr: Możesz utworzyć wylot dla jednego z ograniczeń, aby można go było usunąć i ponownie dodać.


Utworzyłem nowy projekt i dodałem widok o stałym rozmiarze w środku. Wiązania pokazano na poniższym obrazku.

Ograniczenia dotyczące najmniejszego przykładu, jaki przychodzi mi do głowy.

Następnie dodałem wylot dla widoku, który ma się obracać, oraz dla wiązania wyrównania środek x.

@property (weak, nonatomic) IBOutlet UIView *rotatingView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *xAlignmentContstraint;

Później viewDidAppearobliczam nowy punkt kontrolny

UIView *view = self.rotatingView;

CGPoint rotationPoint = // The point I'm rotating around... (only X differs)
CGPoint anchorPoint = CGPointMake((rotationPoint.x-CGRectGetMinX(view.frame))/CGRectGetWidth(view.frame),
                                  (rotationPoint.y-CGRectGetMinY(view.frame))/CGRectGetHeight(view.bounds));

CGFloat xCenterDifference = rotationPoint.x-CGRectGetMidX(view.frame);

view.layer.anchorPoint = anchorPoint;

Następnie usuwam ograniczenie, dla którego mam gniazdko, tworzę nowy, który jest przesunięty i dodaję go ponownie. Następnie mówię widokowi ze zmienionym ograniczeniem, że musi zaktualizować ograniczenia.

[self.view removeConstraint:self.xAlignmentContstraint];
self.xAlignmentContstraint = [NSLayoutConstraint constraintWithItem:self.rotatingView
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0
                                                           constant:xDiff];
[self.view addConstraint:self.xAlignmentContstraint];
[self.view needsUpdateConstraints];

Na koniec dodaję animację obrotu do widoku obrotowego.

CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotate.toValue = @(-M_PI_2);
rotate.autoreverses = YES;
rotate.repeatCount = INFINITY;
rotate.duration = 1.0;
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 

[view.layer addAnimation:rotate forKey:@"myRotationAnimation"];

Wygląda na to, że obracająca się warstwa pozostaje wyśrodkowana (a powinna) nawet podczas obracania urządzenia lub w inny sposób powodująca aktualizację wiązań. Nowe wiązanie i zmieniony punkt kontrolny wizualnie znoszą się wzajemnie.


1
To by działało w mojej konkretnej sytuacji, nie wygląda na to, żeby ładne ogólne rozwiązanie było możliwe.
jrturton,

1

Moje obecne rozwiązanie polega na ręcznym dostosowaniu położenia warstwy w viewDidLayoutSubviews. Ten kod może być również użyty w layoutSubviewspodklasie widoku, ale w moim przypadku mój widok jest widokiem najwyższego poziomu wewnątrz kontrolera widoku, więc oznaczało to, że nie musiałem tworzyć podklasy UIView.

Wydaje się, że to zbyt duży wysiłek, więc inne odpowiedzi są mile widziane.

-(void)viewDidLayoutSubviews
{
    for (UIView *view in self.view.subviews)
    {
        CGPoint anchorPoint = view.layer.anchorPoint;
        // We're only interested in views with a non-standard anchor point
        if (!CGPointEqualToPoint(CGPointMake(0.5, 0.5),anchorPoint))
        {
            CGFloat xDifference = anchorPoint.x - 0.5;
            CGFloat yDifference = anchorPoint.y - 0.5;
            CGPoint currentPosition = view.layer.position;

            // Use transforms if we can, otherwise manually calculate the frame change
            // Assuming a transform is in use since we are changing the anchor point. 
            if (CATransform3DIsAffine(view.layer.transform))
            {
                CGAffineTransform current = CATransform3DGetAffineTransform(view.layer.transform);
                CGAffineTransform invert = CGAffineTransformInvert(current);
                currentPosition = CGPointApplyAffineTransform(currentPosition, invert);
                currentPosition.x += (view.bounds.size.width * xDifference);
                currentPosition.y += (view.bounds.size.height * yDifference);
                currentPosition = CGPointApplyAffineTransform(currentPosition, current);
            }
            else
            {
                CGFloat transformXRatio = view.bounds.size.width / view.frame.size.width;

                if (xDifference < 0)
                    transformXRatio = 1.0/transformXRatio;

                CGFloat transformYRatio = view.bounds.size.height / view.frame.size.height;
                if (yDifference < 0)
                    transformYRatio = 1.0/transformYRatio;

                currentPosition.x += (view.bounds.size.width * xDifference) * transformXRatio;
                currentPosition.y += (view.bounds.size.height * yDifference) * transformYRatio;
            }
            view.layer.position = currentPosition;
        }

    }
}

1

Zainspirowany odpowiedzią mojego Matta, postanowiłem spróbować innego podejścia. Można użyć widoku kontenera z odpowiednio zastosowanymi ograniczeniami. Widok ze zmodyfikowanym punktem zakotwiczenia można następnie umieścić w widoku kontenera, używając masek autoregulacji i wyraźnego ustawienia ramki, tak jak za dawnych złych czasów.

W każdym razie działa to wspaniale, jak na moją sytuację. Widoki są konfigurowane tutaj w viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UIView *redView = [UIView new];
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[redView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(redView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[redView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(redView)]];
    self.redView = redView;

    UIView *greenView = [UIView new];
    greenView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    greenView.layer.anchorPoint = CGPointMake(1.0, 0.5);
    greenView.frame = redView.bounds;
    greenView.backgroundColor = [UIColor greenColor];
    [redView addSubview:greenView];
    self.greenView = greenView;

    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = 0.005;
    self.redView.layer.sublayerTransform = perspective;
}

Nie ma znaczenia, że ​​ramki widoku czerwonego są w tym momencie zerowe, ze względu na maskę autoreferencji w widoku zielonym.

Dodałem transformację rotacyjną do metody akcji i był to wynik:

wprowadź opis obrazu tutaj

Wydawało się, że gubi się podczas obracania urządzenia, więc dodałem do metody viewDidLayoutSubviews:

-(void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    CATransform3D transform = self.greenView.layer.transform;
    self.greenView.layer.transform = CATransform3DIdentity;
    self.greenView.frame = self.redView.bounds;
    self.greenView.layer.transform = transform;
    [CATransaction commit];

}

Zastanawiam się, czy w Twoim przypadku nie byłoby łatwiej po prostu zostawić w view.layerspokoju i wykonać całą pracę w podwarstwie o formacie view.layer. Innymi słowy, widok byłby po prostu obiektem nadrzędnym, a wszystkie transformacje rysunkowe i podwarstwy itd. Byłyby o poziom niższy, bez ograniczeń.
mat.

1
Okay, zainspirowany twoim rozwiązaniem subview, dodałem to do mojego eseju! Dzięki, że pozwoliłeś mi zagrać w twojej piaskownicy ...
Matt

0

Myślę, że tą metodą pokonujesz cel autoukładu. Wspomniałeś, że szerokość i prawa krawędź zależą od superwizji, więc dlaczego nie dodać po prostu ograniczeń wzdłuż tej linii myślenia?

Porzuć paradygmat anchorPoint / transform i spróbuj:

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:layerView
                             attribute:NSLayoutAttributeRight
                             relatedBy:NSLayoutRelationEqual 
                                toItem:self.view 
                             attribute:NSLayoutAttributeWidth 
                            multiplier:1.0f
                              constant:-somePadding]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:layerView
                             attribute:NSLayoutAttributeWidth
                             relatedBy:NSLayoutRelationEqual 
                                toItem:someViewWeDependTheWidthOn
                             attribute:NSLayoutAttributeWidth 
                            multiplier:0.5f // because you want it to be half of someViewWeDependTheWidthOn
                              constant:-20.0f]]; // your 20pt offset from the left

Te NSLayoutAttributeRightśrodki ograniczające dokładnie jak anchorPoint = CGPointMake(1.0, 0.5), a NSLayoutAttributeWidthograniczeniem jest grubsza odpowiada poprzednia kodeksu NSLayoutAttributeLeft.


Dziękuję za odpowiedź, ale nie mogę „stracić punktu zaczepienia / paradygmatu transformacji”. Muszę zastosować transformację i dostosować punkt zakotwiczenia, aby dokonać poprawnej transformacji.
jrturton

Aby wyjaśnić dokładniej, w tym konkretnym przypadku przekształcony widok będzie musiał czasami składać się na ekranie, obracając się wzdłuż osi Y na jego skrajnej prawej krawędzi. Dlatego punkt kotwiczenia musi zostać przesunięty. Szukam też rozwiązania ogólnego.
jrturton,

Oczywiście nadal możesz zachować anchorPoint, chodzi mi o to, że nie powinieneś używać go do pomiarów. System autoukładu UIView powinien być niezależny od przekształceń CALayer. Więc UIView: układ, CALayer: wygląd / animacje
John Estropia

1
Powinno, ale tak nie jest. Czy faktycznie próbowałeś tego? Zmiana punktu zakotwiczenia powoduje przesunięcie warstwy po wykonaniu pracy układu automatycznego. Twoje rozwiązanie nie działa ze zmodyfikowanym punktem kontrolnym.
jrturton

Tak, mogę używać różnych punktów zakotwiczenia dla moich widoków z ograniczeniami. Twoja odpowiedź viewDidLayoutSubviewspowinna to naprawić; positionzawsze się zgadza anchorPoint. Moja odpowiedź pokazuje jedynie, jak zdefiniować ograniczenie dla transformacji tożsamości.
John Estropia,

0

To pytanie i odpowiedzi zainspirowały mnie do rozwiązania własnych problemów z Autolayout i skalowaniem, ale z scrollviews. Przykład mojego rozwiązania stworzyłem na githubie:

https://github.com/hansdesmedt/AutoLayout-scrollview-scale

To jest przykład UIScrollView z niestandardowym stronicowaniem całkowicie wykonanym w AutoLayout i jest skalowalny (CATransform3DMakeScale) z długim naciśnięciem i dotknięciem, aby powiększyć. Kompatybilny z iOS 6 i 7.


0

To duży temat i nie przeczytałem wszystkich komentarzy, ale miałem ten sam problem.

Miałem widok z XIB z autoukładem. Chciałem zaktualizować jego właściwość transform. Osadzenie widoku w widoku kontenera nie rozwiązuje mojego problemu, ponieważ autoukład działał dziwnie w widoku kontenera. Dlatego właśnie dodałem drugi widok kontenera, aby zawierał widok kontenera, który zawiera mój widok i zastosowałem w nim transformacje.


0

tl; dr Powiedzmy, że zmieniłeś punkt kontrolny na (0, 0). Punkt kontrolny znajduje się teraz w lewym górnym rogu. Zawsze można zobaczyć wyraz centrum w układzie automatycznym, należy pomyśleć górnym rogu .

Dostosowując anchorPoint, po prostu zmieniasz semantykę AutoLayout. Układ automatyczny nie będzie kolidował z punktem kotwiczenia ani odwrotnie. Jeśli tego nie rozumiesz, będziesz się źle bawić .


Przykład:

Rysunek A. Brak modyfikacji punktów kontrolnych

#Before changing anchor point to top-left
view.size == superview.size
view.center == superview.center

Rysunek B. Punkt zakotwiczenia zmieniono na lewy górny

view.layer.anchorPoint = CGPointMake(0, 0)
view.size == superview.size
view.center == superview.topLeft                <----- L0-0K, center is now top-left

Rysunek A i rysunek B wyglądają dokładnie tak samo. Nic się nie zmieniło. Zmieniła się tylko definicja tego, do którego centrum się odnosi.


To wszystko dobrze i dobrze, ale nie odpowiada na pytanie, co zrobić, gdy twoje ograniczenia nie są związane z centrum.
jrturton,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.