Wie passe ich den Ankerpunkt eines CALayers an, wenn das automatische Layout verwendet wird?

161

Hinweis : Seit diese Frage gestellt wurde, haben sich die Dinge weiterentwickelt. siehe hier für eine gute aktuelle Übersicht.


Vor dem automatischen Layout können Sie den Ankerpunkt der Ebene einer Ansicht ändern, ohne die Ansicht zu verschieben, indem Sie den Rahmen speichern, den Ankerpunkt festlegen und den Rahmen wiederherstellen.

In einer Welt mit automatischem Layout setzen wir keine Frames mehr, aber Einschränkungen scheinen nicht der Aufgabe gewachsen zu sein, die Position einer Ansicht wieder an die gewünschte Stelle anzupassen. Sie können die Einschränkungen hacken, um Ihre Ansicht neu zu positionieren. Bei Rotations- oder anderen Größenänderungsereignissen werden diese jedoch wieder ungültig.

Die folgende gute Idee funktioniert nicht, da sie eine "ungültige Paarung von Layoutattributen (links und Breite)" erstellt:

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]];

Ich wollte hier den linken Rand layerViewder Ansicht mit dem angepassten Ankerpunkt auf die Hälfte ihrer Breite plus 20 einstellen (den Abstand, den ich vom linken Rand der Übersicht einfügen möchte).

Ist es möglich, den Ankerpunkt in einer Ansicht mit automatischem Layout zu ändern, ohne die Position einer Ansicht zu ändern? Muss ich fest codierte Werte verwenden und die Einschränkung bei jeder Drehung bearbeiten? Ich hoffe nicht.

Ich muss den Ankerpunkt ändern, damit ich beim Anwenden einer Transformation auf die Ansicht den richtigen visuellen Effekt erhalte.

jrturton
quelle
Angesichts dessen, was Sie hier haben, scheint es, dass Sie ohnehin mehrdeutige Layouts haben werden, selbst wenn Sie den obigen Code zum Laufen gebracht haben. Woher layerViewweiß seine Breite? Klebt es mit der rechten Seite an etwas anderem?
John Estropia
//Size-related constraints that work fineDies wird in der - Breite und Höhe der Ebenenansicht von der Übersichtsansicht abgeleitet.
Jrturton

Antworten:

360

[BEARBEITEN: Warnung: Die gesamte nachfolgende Diskussion wird möglicherweise durch iOS 8 veraltet oder zumindest stark gemildert, was möglicherweise nicht mehr den Fehler macht, das Layout zum Zeitpunkt der Anwendung einer Ansichtstransformation auszulösen.]

Autolayout vs. View-Transformationen

Autolayout spielt bei Ansichtstransformationen überhaupt nicht gut. Der Grund ist, soweit ich das beurteilen kann, dass Sie sich nicht mit dem Rahmen einer Ansicht herumschlagen sollen, die eine Transformation enthält (außer der Standardidentitätstransformation) - aber genau das macht Autolayout. Die Art und Weise, wie Autolayout funktioniert, besteht darin, dass layoutSubviewszur Laufzeit alle Einschränkungen durchlaufen und die Frames aller Ansichten entsprechend festgelegt werden.

Mit anderen Worten, die Einschränkungen sind keine Magie; Sie sind nur eine To-Do-Liste. layoutSubviewsHier wird die To-Do-Liste erstellt. Und das durch Setzen von Frames.

Ich kann nicht anders, als dies als Fehler zu betrachten. Wenn ich diese Transformation auf eine Ansicht anwende:

v.transform = CGAffineTransformMakeScale(0.5,0.5);

Ich erwarte, dass die Ansicht mit ihrer Mitte an derselben Stelle wie zuvor und in halber Größe angezeigt wird. Aber je nach den Einschränkungen sehe ich das möglicherweise überhaupt nicht.

[Eigentlich gibt es hier eine zweite Überraschung: Das Anwenden einer Transformation auf eine Ansicht löst sofort das Layout aus. Dies scheint mir ein weiterer Fehler zu sein. Oder vielleicht ist es das Herzstück des ersten Fehlers. Was ich erwarten würde, ist, dass ich zumindest bis zur Layoutzeit mit einer Transformation davonkommen kann, z. B. wenn das Gerät gedreht wird - genauso wie ich mit einer Rahmenanimation bis zur Layoutzeit davonkommen kann. Tatsächlich ist die Layoutzeit jedoch unmittelbar, was einfach falsch erscheint.]

Lösung 1: Keine Einschränkungen

Eine aktuelle Lösung besteht darin, alle Einschränkungen, die sie betreffen, zu entfernen, wenn ich eine semipermanente Transformation auf eine Ansicht anwenden möchte (und sie nicht nur vorübergehend wackeln möchte). Leider führt dies normalerweise dazu, dass die Ansicht vom Bildschirm verschwindet, da das automatische Layout immer noch stattfindet und es jetzt keine Einschränkungen gibt, die uns mitteilen, wo die Ansicht platziert werden soll. Zusätzlich zum Entfernen der Einschränkungen habe ich die Ansicht translatesAutoresizingMaskIntoConstraintsauf JA gesetzt. Die Ansicht funktioniert jetzt auf die alte Art und Weise, ohne dass dies durch Autolayout beeinträchtigt wird. (Es ist offensichtlich von Autolayout betroffen, aber die impliziten Einschränkungen der Autoresize-Maske führen dazu, dass sein Verhalten genau so ist, wie es vor dem Autolayout war.)

Lösung 2: Verwenden Sie nur geeignete Einschränkungen

Wenn dies etwas drastisch erscheint, besteht eine andere Lösung darin, die Einschränkungen so festzulegen, dass sie mit einer beabsichtigten Transformation korrekt funktionieren. Wenn eine Ansicht nur nach ihrer internen festen Breite und Höhe dimensioniert und beispielsweise nur nach ihrer Mitte positioniert wird, funktioniert meine Skalentransformation wie erwartet. In diesem Code entferne ich die vorhandenen Einschränkungen für eine Unteransicht ( otherView) und ersetze sie durch diese vier Einschränkungen, indem ich ihr eine feste Breite und Höhe gebe und sie nur durch ihre Mitte fixiere . Danach funktioniert meine Skalentransformation:

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]];

Das Ergebnis ist, dass das automatische Layout den Rahmen der Ansicht nicht berührt, wenn Sie keine Einschränkungen haben, die sich auf den Rahmen einer Ansicht auswirken. Dies ist genau das, wonach Sie suchen, wenn eine Transformation beteiligt ist.

Lösung 3: Verwenden Sie eine Unteransicht

Das Problem bei beiden oben genannten Lösungen besteht darin, dass wir die Vorteile von Einschränkungen verlieren, um unsere Sichtweise zu positionieren. Hier ist eine Lösung, die das löst. Beginnen Sie mit einer unsichtbaren Ansicht, deren Aufgabe es ist, ausschließlich als Host zu fungieren, und verwenden Sie Einschränkungen, um sie zu positionieren. Fügen Sie darin die reale Ansicht als Unteransicht ein. Verwenden Sie Einschränkungen, um die Unteransicht in der Hostansicht zu positionieren, beschränken Sie diese Einschränkungen jedoch auf Einschränkungen, die sich beim Anwenden einer Transformation nicht wehren.

Hier ist eine Illustration:

Geben Sie hier die Bildbeschreibung ein

Die weiße Ansicht ist die Hostansicht. Sie sollen so tun, als wäre es transparent und daher unsichtbar. Die rote Ansicht ist die Unteransicht, die durch Anheften der Mitte an die Mitte der Hostansicht positioniert wird. Jetzt können wir die rote Ansicht problemlos skalieren und um ihre Mitte drehen, und die Abbildung zeigt, dass wir dies getan haben:

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

In der Zwischenzeit halten die Einschränkungen für die Hostansicht sie beim Drehen des Geräts an der richtigen Stelle.

Lösung 4: Verwenden Sie stattdessen Layer-Transformationen

Verwenden Sie anstelle von Ansichtstransformationen Ebenentransformationen, die kein Layout auslösen und daher keinen unmittelbaren Konflikt mit Einschränkungen verursachen.

Zum Beispiel kann diese einfache Animation der "Pochen" -Ansicht unter Autolayout unterbrochen werden:

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

Auch wenn sich die Größe der Ansicht am Ende nicht geändert hat, wird lediglich transformdas Layout der Ursachen festgelegt, und Einschränkungen können dazu führen, dass die Ansicht herumspringt. (Fühlt sich das wie ein Fehler an oder was?) Wenn wir jedoch dasselbe mit Core Animation tun (mithilfe einer CABasicAnimation und Anwenden der Animation auf die Ebene der Ansicht), geschieht das Layout nicht und es funktioniert einwandfrei:

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];
matt
quelle
3
Die Transformation verursacht nicht das Problem, sondern setzt nur den neuen Ankerpunkt. Ich werde versuchen, die Einschränkungen zu entfernen und die Autoresizing-Maske auszuprobieren.
Jrturton
1
Ich verstehe hier nichts. Lösung 4: Wenn eine Transformation auf eine Backing-Ebene angewendet wird, wirkt sich diese Transformation auch auf das Autolayout aus, da sich die Transformationseigenschaft der Ansicht auf die Transformation der Backing-Ebene bezieht
Luca Bartoletti
1
@ Andy im Gegenteil, unter iOS 8 ist das Problem komplett verschwunden und meine ganze Antwort ist unnötig
matt
1
@ Sunkas habe ich in deiner Ausgabe beantwortet.
Ben Sinclair
2
Unter iOS 8 löst die
Ebenentransformation
41

Ich hatte eine ähnliche Isuue und hörte gerade Back vom Autolayout-Team bei Apple. Sie schlagen vor, den von Matt vorgeschlagenen Ansatz für die Containeransicht zu verwenden, erstellen jedoch eine Unterklasse von UIView, um layoutSubviews zu überschreiben und dort benutzerdefinierten Layoutcode anzuwenden. Dies funktioniert wie ein Zauber

Die Header-Datei sieht so aus, dass Sie die gewünschte Unteransicht direkt über den Interface Builder verknüpfen können

#import <UIKit/UIKit.h>

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

und die m-Datei wendet den speziellen Code folgendermaßen an:

#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

Wie Sie sehen können, wird beim ersten Aufruf der Mittelpunkt der Ansicht erfasst und diese Position bei weiteren Aufrufen wiederverwendet, um die Ansicht entsprechend zu platzieren. Dies überschreibt den Autolayout-Code in dem Sinne, dass er nach [super layoutSubviews] stattfindet; welches Autolayout-Code enthält.

Auf diese Weise muss Autolayout nicht mehr vermieden werden. Sie können jedoch ein eigenes Autolayout erstellen, wenn das Standardverhalten nicht mehr angemessen ist. Natürlich können Sie viel kompliziertere Dinge anwenden als in diesem Beispiel, aber das war alles, was ich brauchte, da meine App nur den Porträtmodus verwenden kann.

sensslen
quelle
5
Vielen Dank für die Veröffentlichung. Seit ich meine Antwort geschrieben habe, habe ich den Wert des Überschreibens definitiv verstanden layoutSubviews. Ich möchte nur hinzufügen, wenn auch, dass Apple hier nur macht Sie tun , was ich denke , sie sollten (bei der Umsetzung von Autolayout) getan haben , die ganze Zeit .
Matt
Sie haben Recht, aber das hängt wohl nicht wirklich mit dem Problem selbst zusammen. Da sie den obigen Code zur Verfügung gestellt haben, bin ich damit zufrieden!
Sensslen
4
Es ist traurig und absurd, dass das Autolayout-Team selbst empfiehlt, eine benutzerdefinierte Container-Unterklasse zu erstellen, um zu umgehen, wie Autolayout das langjährige und intuitive Verhalten im Ansichtssystem unterbricht.
Algen
8

Ich finde einen einfachen Weg. Und es funktioniert unter iOS 8 und iOS 9.

Wie Ankerpunkt anpassen, wenn Sie rahmenbasiertes Layout verwenden:

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

Wenn Sie den Anker der Ansicht mit automatischem Layout anpassen, tun Sie dasselbe, jedoch auf Einschränkungen. Wenn sich anchorPoint von (0,5, 0,5) auf (1, 0,5) ändert, bewegt sich die Ebenenansicht mit einem Abstand von der halben Länge der Ansichtsbreite nach links. Sie müssen dies also kompensieren.

Ich verstehe die Einschränkung in der Frage nicht. Nehmen wir also an, dass Sie eine centerX-Einschränkung relativ zu superView centerX mit einer Konstanten hinzufügen: layerView.centerX = superView.centerX + Konstante

layerView.layer.anchorPoint = CGPoint(1, 0.5)
let centerXConstraint = .....
centerXConstraint.constant = centerXConstraint.constant + layerView.bounds.size.width/2
seedante
quelle
Ein Glas feinstes Bier für dich, mein Freund. Dies ist so gut Workaround: D
Błażej
3

Wenn Sie das automatische Layout verwenden, sehe ich nicht, wie das manuelle Festlegen der Position auf lange Sicht funktioniert, da das automatische Layout möglicherweise den Positionswert beeinträchtigt, den Sie bei der Berechnung des eigenen Layouts festgelegt haben.

Vielmehr müssen die Layouteinschränkungen selbst geändert werden, um die durch das Setzen des Ankerpunkts hervorgerufenen Änderungen auszugleichen. Die folgende Funktion erledigt dies für nicht transformierte Ansichten.

/**
  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];
}

Ich gebe zu, dass dies wahrscheinlich nicht alles ist, was Sie sich erhofft haben, da der einzige Grund, warum Sie den anchorPoint ändern möchten, normalerweise darin besteht, eine Transformation festzulegen. Dies würde eine komplexere Funktion erfordern, die die Layoutbeschränkungen aktualisiert, um alle Rahmenänderungen widerzuspiegeln , die durch die Transformationseigenschaft selbst verursacht werden könnten. Dies ist schwierig, da Transformationen viel zum Frame beitragen können. Eine Skalierungs- oder Rotationstransformation würde den Rahmen größer machen, sodass wir alle Breiten- oder Höhenbeschränkungen usw. aktualisieren müssten.

Wenn Sie die Transformation nur für eine temporäre Animation verwenden, kann das oben Genannte ausreichen, da ich nicht glaube, dass das automatische Layout verhindert, dass die Animation während des Flugs Bilder darstellt, die rein vorübergehende Verstöße gegen die Einschränkungen darstellen.

Algen
quelle
Ich benutze leider Transformationen. Das Festlegen der Position ist in Ordnung, da die Methode jedes Mal aufgerufen wird, wenn die Ansicht angelegt wird, damit sie nicht überlastet wird. Ich denke, das Überschreiben von layoutRect ist möglicherweise eine bessere Lösung (in einer Ihrer früheren Antworten erwähnt), aber ich habe es noch nicht ausprobiert.
Jrturton
Hier ist übrigens ein Prüfstand, den ich benutzt habe. Es wäre großartig, wenn jemand dies herausfinden würde: github.com/algal/AutolayoutAnchorPointTestRig
Alge
Dies sollte die akzeptierte Antwort sein. Bei der Antwort von @ matt geht es nicht einmal um den Ankerpunkt.
Iulian Onofrei
3

tl: dr: Sie können eine Steckdose für eine der Einschränkungen erstellen, damit diese entfernt und wieder hinzugefügt werden kann.


Ich habe ein neues Projekt erstellt und eine Ansicht mit einer festen Größe in der Mitte hinzugefügt. Die Einschränkungen sind in der Abbildung unten dargestellt.

Die Einschränkungen für das kleinste Beispiel, das ich mir vorstellen kann.

Als nächstes habe ich einen Auslass für die Ansicht hinzugefügt, die gedreht werden soll, und für die Einschränkung der Ausrichtung von Mitte x.

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

Später viewDidAppearberechne ich den neuen Ankerpunkt

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;

Dann entferne ich die Einschränkung, für die ich eine Steckdose habe, erstelle eine neue, die versetzt ist, und füge sie wieder hinzu. Danach sage ich der Ansicht mit der geänderten Einschränkung, dass sie die Einschränkungen aktualisieren muss.

[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];

Zum Schluss füge ich einfach die Rotationsanimation zur rotierenden Ansicht hinzu.

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"];

Die rotierende Ebene sieht so aus, als ob sie zentriert bleibt (was sie sollte), auch wenn das Gerät gedreht wird oder auf andere Weise die Einschränkungen aktualisiert werden. Die neue Einschränkung und der geänderte Ankerpunkt heben sich visuell auf.

David Rönnqvist
quelle
1
Dies würde für meine spezifische Situation funktionieren, es sieht nicht so aus, als wäre eine gute allgemeine Lösung möglich.
Jrturton
1

Meine aktuelle Lösung besteht darin, die Position der Ebene in manuell anzupassen viewDidLayoutSubviews. Dieser Code kann auch layoutSubviewsfür eine Ansichtsunterklasse verwendet werden. In meinem Fall handelt es sich bei meiner Ansicht jedoch um eine Ansicht der obersten Ebene innerhalb eines Ansichtscontrollers. Daher musste ich keine UIView-Unterklasse erstellen.

Es scheint zu viel Aufwand zu sein, daher sind andere Antworten sehr willkommen.

-(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;
        }

    }
}
jrturton
quelle
1

Inspiriert von der Antwort meiner Matte, beschloss ich, einen anderen Ansatz zu versuchen. Eine Containeransicht mit entsprechend angewendeten Einschränkungen kann verwendet werden. Die Ansicht mit dem geänderten Ankerpunkt kann dann mithilfe von Masken zur automatischen Größenanpassung und expliziten Rahmeneinstellung wie in den schlechten alten Zeiten in der Containeransicht platziert werden.

Für meine Situation ist es sowieso ein Vergnügen. Die Ansichten werden hier in viewDidLoad eingerichtet:

- (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;
}

Es spielt keine Rolle, dass die Rahmen für die rote Ansicht zu diesem Zeitpunkt Null sind, da die automatische Größenmaske in der grünen Ansicht angezeigt wird.

Ich habe eine Rotationstransformation für eine Aktionsmethode hinzugefügt, und dies war das Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Es schien sich während der Gerätedrehung von selbst zu verlieren, daher habe ich dies der viewDidLayoutSubviews-Methode hinzugefügt:

-(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];

}
jrturton
quelle
Ich frage mich, ob es in Ihrem Fall nicht einfacher gewesen wäre, einfach in view.layerRuhe zu lassen und all Ihre Arbeit in einer Unterschicht von zu erledigen view.layer. Mit anderen Worten, die Ansicht wäre nur ein Host, und alle Transformationen von Zeichnungen und Unterschichten usw. wären eine Ebene tiefer, die von Einschränkungen nicht beeinflusst wird.
Matt
1
Okay, inspiriert von Ihrer Subview-Lösung, habe ich das meinem Aufsatz hinzugefügt! Danke, dass ich in deinem Sandkasten spielen durfte ...
Matt
0

Ich denke, Sie vereiteln mit dieser Methode den Zweck des Autolayouts. Sie haben erwähnt, dass die Breite und der rechte Rand von der Übersicht abhängen. Warum also nicht einfach Einschränkungen entlang dieser Denkrichtung hinzufügen?

Verlieren Sie das anchorPoint / transform-Paradigma und versuchen Sie:

[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

Die NSLayoutAttributeRightEinschränkung bedeutet genau wie anchorPoint = CGPointMake(1.0, 0.5), und die NSLayoutAttributeWidthEinschränkung entspricht in etwa der Ihres vorherigen Codes NSLayoutAttributeLeft.

John Estropia
quelle
Vielen Dank für Ihre Antwort, aber ich kann das "Ankerpunkt- / Transformationsparadigma" nicht verlieren. Ich muss eine Transformation anwenden und den Ankerpunkt anpassen, um die richtige Transformation durchzuführen.
Jrturton
Zur weiteren Erläuterung muss in diesem speziellen Fall die transformierte Ansicht manchmal durch Drehen entlang der Y-Achse am rechten Rand in den Bildschirm gefaltet werden. Daher muss der Ankerpunkt verschoben werden. Ich suche auch nach einer allgemeinen Lösung.
Jrturton
Natürlich können Sie das immer noch behalten anchorPoint. Mein Punkt ist, dass Sie es nicht für Messungen verwenden sollten. Das UIView-Autolayout-System sollte unabhängig von den CALayer-Transformationen sein. Also UIView: Layout, CALayer: Aussehen / Animationen
John Estropia
1
Es sollte sein, ist es aber nicht. Hast du das tatsächlich versucht? Durch Ändern des Ankerpunkts wird die Position der Ebene nach Abschluss des automatischen Layouts verschoben. Ihre Lösung funktioniert nicht mit einem geänderten Ankerpunkt.
Jrturton
Ja, ich kann verschiedene anchorPoints für meine Ansichten verwenden, die mit Einschränkungen versehen sind. Ihre Antwort in viewDidLayoutSubviewssollte das beheben; positiongeht immer mit anchorPoint. Meine Antwort zeigt lediglich, wie die Einschränkung für die Identitätstransformation definiert wird.
John Estropia
0

Diese Frage und Antworten haben mich dazu inspiriert, meine eigenen Probleme mit Autolayout und Skalierung zu lösen, aber mit Bildlaufansichten. Ich habe ein Beispiel für meine Lösung auf Github erstellt:

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

Dies ist ein Beispiel für eine UIScrollView mit benutzerdefiniertem Paging, das vollständig in AutoLayout erstellt wurde und mit langem Drücken und Tippen zum Zoomen skalierbar ist (CATransform3DMakeScale). iOS 6 und 7 kompatibel.

Hans
quelle
0

Es ist ein großes Thema und ich habe nicht alle Kommentare gelesen, war aber mit dem gleichen Problem konfrontiert.

Ich hatte einen Blick von XIB mit Autolayout. Und ich wollte seine Transformationseigenschaft aktualisieren. Das Einbetten der Ansicht in eine Containeransicht löst mein Problem nicht, da das automatische Layout auf die Containeransicht seltsam wirkte. Aus diesem Grund habe ich gerade die zweite Containeransicht hinzugefügt, um die Containeransicht zu enthalten, die meine Ansicht enthält, und Transformationen darauf angewendet.

plamkata__
quelle
0

Angenommen, Sie haben den Ankerpunkt auf (0, 0) geändert. Der Ankerpunkt befindet sich jetzt oben links. Immer , wenn Sie das Wort sehen Zentrum in Auto - Layout, sollten Sie denken , oben links .

Wenn Sie Ihren anchorPoint anpassen, ändern Sie einfach die Semantik von AutoLayout. Das automatische Layout beeinträchtigt weder Ihren anchorPoint noch umgekehrt. Wenn Sie das nicht verstehen, werden Sie eine schlechte Zeit haben .


Beispiel:

Abbildung A. Keine Ankerpunktänderungen

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

Abbildung B. Der Ankerpunkt wurde nach links oben geändert

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

Abbildung A und Abbildung B sehen genau gleich aus. Nichts hat sich geändert. Nur die Definition dessen, worauf sich das Zentrum bezieht, hat sich geändert.

SEO
quelle
Das ist alles schön und gut, beantwortet aber nicht die Frage, was zu tun ist, wenn Ihre Einschränkungen nicht mit dem Zentrum zusammenhängen.
Jrturton