ฉันจะปรับจุดยึดของ CALayer ได้อย่างไรเมื่อใช้ Auto Layout?


161

หมายเหตุ : สิ่งต่าง ๆ ได้ดำเนินไปนับตั้งแต่มีการถามคำถามนี้ ดูที่นี่สำหรับภาพรวมที่ดีล่าสุด


ก่อนเลย์เอาต์อัตโนมัติคุณสามารถเปลี่ยนจุดยึดของเลเยอร์ของมุมมองโดยไม่ย้ายมุมมองโดยจัดเก็บเฟรมตั้งค่าจุดยึดและกู้คืนเฟรม

ในโลกเลย์เอาต์อัตโนมัติเราไม่ได้กำหนดเฟรมอีกต่อไป แต่ข้อ จำกัด ไม่ได้ขึ้นอยู่กับงานของการปรับตำแหน่งมุมมองกลับไปที่ที่เราต้องการ คุณสามารถแฮกข้อ จำกัด เพื่อเปลี่ยนตำแหน่งมุมมองของคุณ แต่เมื่อหมุนหรือเหตุการณ์การปรับขนาดอื่น ๆ สิ่งเหล่านี้จะไม่ถูกต้องอีกครั้ง

แนวคิดที่สดใสต่อไปนี้ใช้ไม่ได้เนื่องจากสร้าง "การจับคู่แอตทริบิวต์ของรูปแบบไม่ถูกต้อง (ซ้ายและกว้าง)":

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

ความตั้งใจของฉันที่นี่คือการตั้งค่าขอบด้านซ้ายของlayerViewมุมมองที่มีจุดยึดที่ปรับได้ถึงครึ่งหนึ่งของความกว้างของมันบวก 20 (ระยะทางที่ฉันต้องการแทรกจากขอบด้านซ้ายของผู้ควบคุม)

เป็นไปได้ไหมที่จะเปลี่ยนจุดยึดโดยไม่ต้องเปลี่ยนตำแหน่งของมุมมองในมุมมองที่ถูกจัดวางด้วยเลย์เอาต์อัตโนมัติ? ฉันต้องใช้ค่าฮาร์ดโค้ดและแก้ไขข้อ จำกัด ในการหมุนทุกครั้งหรือไม่ ฉันหวังว่าจะไม่

ฉันต้องเปลี่ยนจุดยึดเพื่อที่ว่าเมื่อฉันใช้การแปลงกับมุมมองฉันได้รับผลภาพที่ถูกต้อง


ให้สิ่งที่คุณมีที่นี่ดูเหมือนว่าคุณกำลังจะจบลงด้วยเค้าโครงที่ไม่ชัดเจนแม้ว่าคุณจะได้โค้ดด้านบนทำงาน layerViewความกว้างของมันจะรู้ได้อย่างไร? มันเกาะติดด้านขวาของมันกับสิ่งอื่น ๆ หรือไม่?
John Estropia

ที่กล่าวถึงใน//Size-related constraints that work fine- ความกว้างและความสูงของมุมมองเลเยอร์นั้นมาจากของผู้ควบคุม
jrturton

คำตอบ:


361

[แก้ไข: คำเตือน:การสนทนาที่ตามมาทั้งหมดอาจล้าสมัยหรืออย่างน้อยก็ลดลงอย่างหนักจาก iOS 8 ซึ่งอาจไม่ทำให้เกิดข้อผิดพลาดในการเรียกใช้เค้าโครงในเวลาที่มีการเปลี่ยนมุมมอง]

Autolayout vs. View Transforms

การเล่นอัตโนมัติไม่สามารถเล่นได้ดีนักเมื่อดูการแปลง เหตุผลเท่าที่ฉันสามารถแยกแยะได้ก็คือคุณไม่ควรยุ่งกับกรอบของมุมมองที่มีการแปลง (นอกเหนือจากการแปลงค่าเริ่มต้น) - แต่นั่นคือสิ่งที่ autolayout ทำ วิธีการทำงานอัตโนมัติของการเปิดใช้งานอัตโนมัติคือในlayoutSubviewsรันไทม์นั้นเต็มไปด้วยข้อ จำกัด ทั้งหมดและกำหนดกรอบของมุมมองทั้งหมดตามลำดับ

กล่าวอีกนัยหนึ่งข้อ จำกัด ไม่ใช่เวทมนตร์ พวกเขาเป็นเพียงรายการที่ต้องทำ layoutSubviewsเป็นที่ที่ต้องทำรายการสิ่งที่ต้องทำ และทำได้โดยการตั้งค่าเฟรม

ฉันไม่สามารถช่วยเรื่องนี้เป็นข้อผิดพลาด ถ้าฉันใช้การแปลงนี้กับมุมมอง:

v.transform = CGAffineTransformMakeScale(0.5,0.5);

ฉันคาดว่าจะเห็นมุมมองปรากฏขึ้นพร้อมศูนย์กลางของมันในสถานที่เดียวกับก่อนและครึ่งขนาด แต่ขึ้นอยู่กับข้อ จำกัด ของมันนั่นอาจไม่ใช่สิ่งที่ฉันเห็นเลย

[ที่จริงแล้วมีความประหลาดใจที่สองที่นี่: การใช้การแปลงเป็นมุมมองเรียกรูปแบบทันที ดูเหมือนว่าฉันจะเป็นข้อผิดพลาดอื่น หรืออาจเป็นหัวใจของข้อผิดพลาดแรก สิ่งที่ฉันคาดหวังก็คือสามารถเปลี่ยนแปลงไปอย่างน้อยก็จนกว่าจะถึงเวลาเลย์เอาต์เช่นอุปกรณ์หมุนได้เช่นเดียวกับที่ฉันสามารถหนีด้วยภาพเคลื่อนไหวเฟรมจนกระทั่งถึงเวลาเลย์เอาต์ แต่ในความเป็นจริงเวลาเลย์เอาต์ทันทีซึ่งดูเหมือนผิดปกติ]

โซลูชันที่ 1: ไม่มีข้อ จำกัด

วิธีแก้ปัญหาหนึ่งในปัจจุบันคือถ้าฉันจะใช้การแปลงกึ่งโปร่งใสกับมุมมอง (และไม่เพียงแค่โยกเยกมันชั่วคราวอย่างใด) เพื่อลบข้อ จำกัด ทั้งหมดที่มีผลต่อมัน น่าเสียดายที่นี่เป็นสาเหตุทำให้มุมมองหายไปจากหน้าจอเนื่องจากการเปิดอัตโนมัติยังคงเกิดขึ้นและตอนนี้ไม่มีข้อ จำกัด ใด ๆ ที่จะบอกเราว่าต้องวางมุมมองไว้ที่ใด ดังนั้นนอกเหนือจากการลบข้อ จำกัด ฉันตั้งค่ามุมมองเป็นtranslatesAutoresizingMaskIntoConstraintsใช่ ตอนนี้มุมมองใช้งานได้แบบเก่าโดยไม่ได้รับผลกระทบจากการชำระอัตโนมัติ (มันเป็นผลกระทบจาก autolayout อย่างเห็นได้ชัด แต่นัย autoresizing จำกัด หน้ากากก่อให้เกิดพฤติกรรมของมันจะเป็นเช่นเดียวกับที่มันเป็นก่อน autolayout.)

โซลูชันที่ 2: ใช้ข้อ จำกัด ที่เหมาะสมเท่านั้น

หากดูเหมือนว่ารุนแรงเล็กน้อยวิธีแก้ไขอื่นคือการกำหนดข้อ จำกัด ให้ทำงานอย่างถูกต้องกับการแปลงที่ตั้งใจไว้ หากมุมมองมีการกำหนดขนาดอย่างหมดจดโดยความกว้างและความสูงคงที่ภายในและวางตำแหน่งไว้ตรงกลางอย่างหมดจดตัวอย่างเช่นการแปลงสเกลของฉันจะทำงานตามที่ฉันคาดไว้ ในรหัสนี้ฉันจะลบข้อ จำกัด ที่มีอยู่ในมุมมองย่อย ( otherView) และแทนที่ด้วยข้อ จำกัด ทั้งสี่ทำให้มีความกว้างและความสูงคงที่และตรึงไว้ที่กึ่งกลางของมัน หลังจากนั้นการเปลี่ยนสเกลของฉันก็ใช้งานได้:

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

ผลที่สุดคือถ้าคุณไม่มีข้อ จำกัด ที่ส่งผลต่อเฟรมของมุมมองการเปลี่ยนอัตโนมัติจะไม่แตะที่เฟรมของมุมมองซึ่งเป็นสิ่งที่คุณทำหลังจากที่มีการเปลี่ยนแปลง

โซลูชันที่ 3: ใช้มุมมองย่อย

ปัญหาของทั้งสองวิธีข้างต้นคือเราเสียประโยชน์จากข้อ จำกัด ในการกำหนดมุมมองของเรา นี่คือทางออกที่แก้ปัญหานั้นได้ เริ่มต้นด้วยมุมมองที่มองไม่เห็นซึ่งมีหน้าที่ทำหน้าที่เป็นเจ้าภาพเพียงอย่างเดียวและใช้ข้อ จำกัด เพื่อวางตำแหน่ง ภายในนั้นให้วางมุมมองที่แท้จริงเป็นมุมมองย่อย ใช้ข้อ จำกัด เพื่อวางตำแหน่งมุมมองย่อยภายในมุมมองโฮสต์ แต่ จำกัด ข้อ จำกัด เหล่านั้นไว้กับข้อ จำกัด ที่จะไม่ต่อสู้เมื่อเราใช้การแปลง

นี่คือภาพประกอบ:

ป้อนคำอธิบายรูปภาพที่นี่

มุมมองสีขาวคือมุมมองโฮสต์ คุณควรจะแกล้งทำเป็นว่ามันโปร่งใสและมองไม่เห็นด้วยเหตุนี้ มุมมองสีแดงคือมุมมองย่อยโดยวางตำแหน่งกึ่งกลางไปยังกึ่งกลางของมุมมองโฮสต์ ตอนนี้เราสามารถปรับขนาดและหมุนมุมมองสีแดงรอบจุดศูนย์กลางได้โดยไม่มีปัญหาและภาพประกอบจริงแสดงให้เห็นว่าเราได้ทำไปแล้ว:

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

และในขณะเดียวกันข้อ จำกัด ในมุมมองโฮสต์นั้นเก็บไว้ในตำแหน่งที่ถูกต้องในขณะที่เราหมุนอุปกรณ์

โซลูชันที่ 4: ใช้การแปลงเลเยอร์แทน

แทนที่จะใช้การแปลงมุมมองให้ใช้การแปลงเลเยอร์ซึ่งไม่ทริกเกอร์โครงร่างและไม่ทำให้เกิดข้อขัดแย้งกับข้อ จำกัด ในทันที

ตัวอย่างเช่นภาพเคลื่อนไหวมุมมอง "throb" ที่เรียบง่ายนี้อาจแตกภายใต้การค้นหาอัตโนมัติ:

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

แม้ว่าในที่สุดแล้วจะไม่มีการเปลี่ยนแปลงขนาดของมุมมองเพียงแค่กำหนดtransformเค้าโครงของสาเหตุที่จะเกิดขึ้นและข้อ จำกัด สามารถทำให้มุมมองกระโดดไปมา (สิ่งนี้รู้สึกเหมือนเป็นแมลงหรืออะไร) แต่ถ้าเราทำสิ่งเดียวกันกับ Core Animation (โดยใช้ CABasicAnimation และนำแอนิเมชันไปใช้กับเลเยอร์ของมุมมอง) เลย์เอาต์ก็ไม่เกิดขึ้นและมันก็ใช้ได้ดี:

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
การแปลงไม่ใช่สิ่งที่ทำให้ฉันเกิดปัญหาเพียงแค่กำหนดจุดยึดใหม่ ฉันจะไปลบข้อ จำกัด และลองหน้ากากปรับขนาดอัตโนมัติแม้ว่า
jrturton

1
ฉันไม่เข้าใจบางสิ่งที่นี่ โซลูชันที่ 4: เมื่อการแปลงถูกนำไปใช้กับเลเยอร์การสำรองข้อมูลที่มีผลต่อการเปลี่ยนอัตโนมัติเช่นกันเนื่องจากคุณสมบัติการแปลงของมุมมองนั้นหมายถึงการเปลี่ยนของเลเยอร์สำรอง
Luca Bartoletti

1
@ ในทางตรงกันข้ามบน iOS 8 ปัญหาจะหายไปอย่างสมบูรณ์และคำตอบทั้งหมดของฉันไม่จำเป็น
แมตต์

1
@Sunkas ฉันตอบในปัญหาของคุณ
Ben Sinclair

2
ใน iOS 8 การแปลงเลเยอร์ยังเป็นต้นเหตุของเลย์เอาต์อัตโนมัติ
CarmeloS

41

ฉันมี Isuue ที่คล้ายกันและเพิ่งได้ยินกลับมาจากทีม Autolayout ที่ Apple พวกเขาแนะนำให้ใช้วิธีการดูคอนเทนเนอร์แบบแมตต์แนะนำ แต่พวกเขาสร้างคลาสย่อยของ UIView เพื่อเขียนทับ layoutSubviews และใช้โค้ดเลย์เอาต์ที่กำหนดเองที่นั่น - มันทำงานได้เหมือนมนต์เสน่ห์

ไฟล์ส่วนหัวมีลักษณะเช่นนั้นเพื่อให้คุณสามารถเชื่อมโยงมุมมองย่อยที่คุณเลือกได้โดยตรงจากตัวสร้างส่วนต่อประสาน

#import <UIKit/UIKit.h>

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

และไฟล์ m ใช้รหัสพิเศษเช่นนั้น:

#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

อย่างที่คุณเห็นมันจะจับจุดกึ่งกลางของมุมมองเมื่อมีการโทรครั้งแรกและนำตำแหน่งนั้นกลับมาใช้ในการโทรเพิ่มเติมเพื่อวางมุมมองให้สอดคล้องกัน สิ่งนี้จะเขียนทับ Autolayout Code ในกรณีดังกล่าวซึ่งเกิดขึ้นหลังจาก [super layoutSubviews]; ซึ่งมีรหัสการชำระอัตโนมัติ

เช่นเดียวกับที่ไม่จำเป็นต้องหลีกเลี่ยง Autolayout อีกต่อไป แต่คุณสามารถสร้าง autolayout ของคุณเองได้เมื่อพฤติกรรมเริ่มต้นไม่เหมาะสมอีกต่อไป แน่นอนคุณสามารถใช้วิธีที่ซับซ้อนกว่าสิ่งที่อยู่ในตัวอย่างนั้น แต่ทั้งหมดนี้ฉันต้องการเพราะแอพของฉันสามารถใช้โหมดแนวตั้งได้เท่านั้น


5
ขอขอบคุณสำหรับการโพสต์นี้. ตั้งแต่ฉันเขียนคำตอบของฉันฉันได้เข้าใจถึงคุณค่าของการเอาชนะlayoutSubviewsอย่างแน่นอน ฉันอยากจะเพิ่มว่า Apple ที่นี่แค่ทำให้คุณทำในสิ่งที่ฉันคิดว่าพวกเขาควรจะทำ (ในการนำ Autolayout ไปใช้) ตลอดเวลา
matt

คุณพูดถูก แต่ฉันคิดว่ามันไม่เกี่ยวข้องกับปัญหานี้จริงๆ เนื่องจากพวกเขาให้รหัสข้างต้นฉันมีความสุขกับสิ่งนั้น!
sensslen

4
มันเป็นเรื่องน่าเศร้าและไร้สาระที่ทีม autolayout แนะนำให้สร้าง subclass ของ container ที่กำหนดเองเพื่อหลีกเลี่ยงการ autolayout แบ่งพฤติกรรมที่มีมานานและใช้งานง่ายในระบบมุมมอง
สาหร่าย

8

ฉันหาวิธีง่ายๆ และใช้งานได้กับ iOS 8 และ iOS 9

เช่นเดียวกับปรับ anchorPoint เมื่อคุณใช้เค้าโครงตามเฟรม:

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

เมื่อคุณปรับมุมมองสมอเรือด้วยเลย์เอาต์อัตโนมัติคุณทำสิ่งเดียวกัน แต่เป็นข้อ จำกัด เมื่อ anchorPoint เปลี่ยนจาก (0.5, 0.5) เป็น (1, 0.5), LayerView จะเลื่อนไปทางซ้ายด้วยระยะทางครึ่งหนึ่งของความกว้างของมุมมองดังนั้นคุณต้องชดเชยสิ่งนี้

ฉันไม่เข้าใจข้อ จำกัด ในคำถามดังนั้นสมมติว่าคุณเพิ่มข้อ จำกัด centerX ที่เกี่ยวข้องกับ superView centerX ด้วยค่าคงที่: layerView.centerX = superView.centerX + ค่าคงที่

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

แก้วเบียร์ที่ดีที่สุดสำหรับคุณเพื่อนของฉัน นี่เป็นวิธีแก้ปัญหาที่ดีมาก: D
Błażej

3

หากคุณใช้การจัดวางอัตโนมัติฉันไม่เห็นว่าการตั้งค่าตำแหน่งด้วยตนเองจะทำงานได้อย่างไรในระยะยาวเพราะรูปแบบอัตโนมัติจะทำให้ค่าตำแหน่งที่คุณตั้งไว้เมื่อคำนวณเค้าโครงของตัวเอง

แต่สิ่งที่จำเป็นคือการแก้ไขข้อ จำกัด ของเลย์เอาต์เพื่อชดเชยการเปลี่ยนแปลงที่เกิดขึ้นจากการตั้งค่า anchorPoint ฟังก์ชั่นต่อไปนี้ทำเช่นนั้นสำหรับมุมมองที่ไม่เปลี่ยนแปลง

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

ฉันยอมรับว่านี่อาจไม่ใช่ทุกอย่างที่คุณหวังเพราะโดยปกติแล้วเหตุผลเดียวที่คุณต้องการแก้ไขจุดยึดคือตั้งค่าการแปลง ซึ่งจะต้องใช้ฟังก์ชันที่ซับซ้อนมากขึ้นซึ่งจะอัปเดตข้อ จำกัด ของเลย์เอาต์เพื่อสะท้อนการเปลี่ยนแปลงของเฟรมทั้งหมดที่อาจเกิดจากคุณสมบัติการแปลงรูปเอง นี่เป็นเรื่องยุ่งยากเพราะการแปลงภาพสามารถทำได้หลายอย่างในเฟรม การเปลี่ยนสเกลหรือการหมุนจะทำให้เฟรมใหญ่ขึ้นดังนั้นเราจำเป็นต้องอัพเดทข้อ จำกัด ความกว้างหรือความสูง ฯลฯ

หากคุณใช้การแปลงเป็นภาพเคลื่อนไหวชั่วคราวเท่านั้นสิ่งที่กล่าวมาข้างต้นอาจพอเพียงเนื่องจากฉันไม่เชื่อว่าเลย์เอาต์อัตโนมัติจะป้องกันไม่ให้ภาพเคลื่อนไหวบนเครื่องบินแสดงภาพที่แสดงถึงการละเมิดข้อ จำกัด อย่างถาวร


ฉันใช้การแปลงอย่างน่าเสียดาย การตั้งค่าตำแหน่งทำได้ดีเพราะวิธีการนั้นถูกเรียกทุกครั้งที่มีการจัดวางมุมมองดังนั้นจึงไม่เกิดการอุดตัน ฉันคิดว่าการแทนที่ layoutRect อาจเป็นวิธีแก้ปัญหาที่ดีกว่า (กล่าวถึงในคำตอบก่อนหน้านี้ของคุณ) แต่ฉันยังไม่ได้ลองเลย
jrturton

อย่างไรก็ตามนี่คืออุปกรณ์ทดสอบที่ฉันใช้อยู่ มันจะดีถ้ามีคนคิดออก: github.com/algal/AutolayoutAnchorPointTestRig
algal

นี่ควรเป็นคำตอบที่ยอมรับได้ คำตอบของ @ matt ไม่ได้เกี่ยวกับจุดยึด
Iulian Onofrei

3

tl: dr:คุณสามารถสร้างทางออกสำหรับหนึ่งในข้อ จำกัด เพื่อให้สามารถลบออกและเพิ่มกลับมาอีกครั้ง


ฉันสร้างโครงการใหม่และเพิ่มมุมมองด้วยขนาดคงที่ในศูนย์กลาง ข้อ จำกัด จะแสดงในภาพด้านล่าง

ข้อ จำกัด สำหรับตัวอย่างที่เล็กที่สุดที่ฉันนึกได้

ต่อไปฉันเพิ่มร้านสำหรับมุมมองที่จะหมุนและสำหรับข้อ จำกัด การจัดตำแหน่งศูนย์ x

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

ต่อมาviewDidAppearฉันก็คำนวณจุดยึดใหม่

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;

จากนั้นฉันก็ลบข้อ จำกัด ที่ฉันมีทางออกให้สร้างใหม่ที่จะชดเชยและเพิ่มกลับมาอีกครั้ง หลังจากนั้นฉันบอกมุมมองพร้อมข้อ จำกัด ที่เปลี่ยนไปซึ่งจำเป็นต้องอัปเดตข้อ จำกัด

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

ในที่สุดฉันก็แค่เพิ่มภาพเคลื่อนไหวการหมุนในมุมมองการหมุน

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

เลเยอร์การหมุนดูเหมือนว่าจะอยู่กึ่งกลาง (ซึ่งควร) แม้ในขณะที่หมุนอุปกรณ์หรือทำให้เกิดการปรับปรุงข้อ จำกัด ข้อ จำกัด ใหม่และจุดยึดที่เปลี่ยนไปนั้นจะยกเลิกซึ่งกันและกันด้วยสายตา


1
สิ่งนี้จะใช้ได้กับสถานการณ์เฉพาะของฉัน แต่ดูเหมือนว่าจะไม่สามารถแก้ไขปัญหาทั่วไปได้
jrturton

1

viewDidLayoutSubviewsวิธีการแก้ปัญหาปัจจุบันของฉันคือการปรับตำแหน่งชั้นด้วยตนเองใน รหัสนี้สามารถใช้layoutSubviewsสำหรับคลาสย่อย view แต่ในกรณีของฉันมุมมองของฉันคือมุมมองระดับบนสุดภายในตัวควบคุมมุมมองดังนั้นนี่หมายความว่าฉันไม่ต้องสร้างคลาสย่อย UIView

ดูเหมือนว่าความพยายามมากเกินไปดังนั้นคำตอบอื่น ๆ ยินดีต้อนรับมากที่สุด

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

เป็นแรงบันดาลใจคำตอบของแมตต์ของฉันฉันตัดสินใจลองวิธีอื่น สามารถใช้มุมมองคอนเทนเนอร์ที่มีข้อ จำกัด อย่างเหมาะสม มุมมองที่มีจุดยึดที่แก้ไขแล้วสามารถวางไว้ในมุมมองคอนเทนเนอร์โดยใช้มาสก์การเปลี่ยนขนาดอัตโนมัติและการตั้งค่ากรอบอย่างชัดเจนเหมือนกับในสมัยก่อน

มันทำงานได้ดีสำหรับสถานการณ์ของฉันอยู่แล้ว มุมมองถูกตั้งค่าที่นี่ใน 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;
}

ไม่สำคัญว่าเฟรมสำหรับมุมมองสีแดงจะเป็นศูนย์ ณ จุดนี้เนื่องจากรูปแบบการปรับขนาดอัตโนมัติในมุมมองสีเขียว

ฉันเพิ่มการแปลงแบบหมุนในวิธีการกระทำและนี่คือผลลัพธ์:

ป้อนคำอธิบายรูปภาพที่นี่

ดูเหมือนว่ามันจะสูญเสียตัวเองระหว่างการหมุนอุปกรณ์ดังนั้นฉันจึงเพิ่มสิ่งนี้ลงในเมธอด 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];

}

ผมสงสัยว่าในกรณีของคุณก็จะไม่ได้รับง่ายขึ้นเพียงแค่ปล่อยให้อยู่คนเดียวและทำทุกการทำงานของคุณในเลเยอร์ย่อยของview.layer view.layerกล่าวอีกนัยหนึ่งมุมมองก็จะเป็นเจ้าภาพและการวาดรูปและ sublayer ทั้งหมดจะถูกลดระดับลงโดยไม่ได้รับผลกระทบจากข้อ จำกัด
แมตต์

1
ตกลงแรงบันดาลใจจากโซลูชันย่อยของคุณฉันได้เพิ่มบทความนั้นลงในบทความ! ขอขอบคุณสำหรับการให้ฉันเล่นใน sandbox ของคุณ ...
แมตต์

0

ฉันคิดว่าคุณเอาชนะจุดประสงค์ของการชำระอัตโนมัติด้วยวิธีการดังกล่าว คุณพูดถึงว่าความกว้างและขอบด้านขวานั้นขึ้นอยู่กับผู้ควบคุมดังนั้นทำไมไม่เพียงเพิ่มข้อ จำกัด ตามแนวความคิดนั้น

ลดจุดยึด / เปลี่ยนกระบวนทัศน์และลอง:

[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

NSLayoutAttributeRightจำกัด วิธีการเหมือนanchorPoint = CGPointMake(1.0, 0.5)และNSLayoutAttributeWidthข้อ จำกัด NSLayoutAttributeLeftคือประมาณเทียบเท่ากับรหัสก่อนหน้าของคุณ


ขอบคุณสำหรับคำตอบของคุณ แต่ฉันไม่สามารถ "เสียจุดยึด / เปลี่ยนกระบวนทัศน์" ฉันต้องใช้การแปลงและปรับจุดยึดเพื่อให้การแปลงถูกต้อง
jrturton

เพื่ออธิบายเพิ่มเติมในกรณีเฉพาะนี้บางครั้งมุมมองที่ถูกแปลงจะต้องพับลงในหน้าจอโดยหมุนตามแกน Y บนขอบด้านขวาสุด ดังนั้นต้องย้ายจุดยึด ฉันกำลังมองหาวิธีการแก้ปัญหาทั่วไป
jrturton

แน่นอนคุณยังสามารถรักษาanchorPointจุดของฉันคือคุณไม่ควรใช้สำหรับการวัด ระบบ Autolayout ของ UIView ควรเป็นอิสระจากการแปลง CALayer ดังนั้น UIView: เลย์เอาต์, CALayer: รูปลักษณ์ / แอนิเมชั่น
John Estropia

1
มันควรจะเป็น แต่มันไม่ใช่ คุณลองสิ่งนี้จริงหรือ การเปลี่ยนจุดยึดจะลดตำแหน่งของเลเยอร์หลังจากเลย์เอาต์อัตโนมัติทำงาน โซลูชันของคุณไม่ทำงานกับจุดยึดที่แก้ไข
jrturton

ใช่ฉันสามารถใช้จุดยึดที่แตกต่างกันสำหรับมุมมองของฉันที่มีข้อ จำกัด คำตอบของคุณviewDidLayoutSubviewsควรแก้ไขที่; positionไปพร้อมกับanchorPointเสมอ คำตอบของฉันเพียงแสดงวิธีการกำหนดข้อ จำกัด สำหรับการแปลงเอกลักษณ์
John Estropia

0

คำถามและคำตอบนี้เป็นแรงบันดาลใจให้ฉันแก้ปัญหาของตัวเองโดยใช้ Autolayout และปรับขนาด แต่มี scrollviews ฉันสร้างตัวอย่างของวิธีการแก้ปัญหาของฉันใน GitHub:

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

นี่คือตัวอย่างของ UIScrollView พร้อมเพจจิ้งที่กำหนดเองอย่างสมบูรณ์ใน AutoLayout และสามารถปรับขนาดได้ (CATransform3DMakeScale) ด้วยการกดค้างและแตะเพื่อซูม รองรับ iOS 6 และ 7


0

มันเป็นหัวข้อใหญ่และฉันยังไม่ได้อ่านความคิดเห็นทั้งหมด แต่กำลังเผชิญปัญหาเดียวกัน

ฉันมีมุมมองจาก XIB พร้อมการชำระอัตโนมัติ และฉันต้องการอัพเดทคุณสมบัติการแปลงสภาพ การฝังมุมมองลงในมุมมองคอนเทนเนอร์ไม่ได้ช่วยแก้ปัญหาของฉันเพราะการเล่นอัตโนมัติได้ทำอย่างประหลาดในมุมมองคอนเทนเนอร์ นั่นเป็นเหตุผลที่ฉันเพิ่งเพิ่มมุมมองคอนเทนเนอร์ที่สองเพื่อให้มีมุมมองคอนเทนเนอร์ที่มีมุมมองของฉันและใช้การแปลงกับมัน


0

tl; dr สมมติว่าคุณเปลี่ยนจุดยึดเป็น (0, 0) จุดยึดตอนนี้อยู่ด้านบนซ้าย เมื่อใดก็ตามที่คุณเห็นศูนย์คำในเลย์เอาต์อัตโนมัติคุณควรคิดซ้ายบน

เมื่อคุณปรับจุดยึดของคุณคุณเพียงแค่เปลี่ยนซีแมนทิกส์ของ AutoLayout เลย์เอาต์อัตโนมัติจะไม่รบกวนจุดยึดหรือในทางกลับกัน ถ้าคุณไม่เข้าใจในเรื่องนี้คุณจะมีช่วงเวลาที่เลวร้าย


ตัวอย่าง:

รูปที่ A. ไม่มีการแก้ไขจุดยึด

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

รูปที่ B. จุดยึดเปลี่ยนเป็นด้านบนซ้าย

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

รูปที่ A และรูป B มีลักษณะเหมือนกันทุกประการ ไม่มีอะไรเปลี่ยนแปลง เพียงความหมายของสิ่งที่ศูนย์หมายถึงการเปลี่ยนแปลง


ทุกอย่างดีและดี แต่ไม่ตอบคำถามว่าต้องทำอย่างไรเมื่อข้อ จำกัด ของคุณไม่เกี่ยวข้องกับศูนย์
jrturton
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.