Thay đổi neo của Calayer của tôi di chuyển chế độ xem


131

Tôi muốn thay đổi anchorPoint, nhưng giữ quan điểm ở cùng một nơi. Tôi đã thử NSLog-ing self.layer.positionself.centercả hai đều giữ nguyên như nhau bất kể thay đổi đối với anchorPoint. Tuy nhiên, quan điểm của tôi di chuyển!

Bất kỳ lời khuyên về cách làm điều này?

self.layer.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);
self.layer.anchorPoint = CGPointMake(1, 1);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);

Đầu ra là:

2009-12-27 20:43:24.161 Type[11289:207] center point: 272.500000 242.500000
2009-12-27 20:43:24.162 Type[11289:207] center point: 272.500000 242.500000

Câu trả lời:


139

Các lớp Geometry và Transforms của Hướng dẫn lập trình hoạt hình lõi giải thích mối quan hệ giữa vị trí của CALayer và các thuộc tính anchorPoint. Về cơ bản, vị trí của một lớp được chỉ định theo vị trí của anchorPoint của lớp. Theo mặc định, anchorPoint của một lớp là (0,5, 0,5), nằm ở trung tâm của lớp. Khi bạn đặt vị trí của lớp, khi đó bạn sẽ đặt vị trí của tâm của lớp trong hệ tọa độ của lớp phủ.

Bởi vì vị trí có liên quan đến anchorPoint của lớp, việc thay đổi neo đó trong khi duy trì cùng một vị trí sẽ di chuyển lớp. Để ngăn chặn chuyển động này, bạn sẽ cần điều chỉnh vị trí của lớp để tính toán cho anchorPoint mới. Một cách tôi đã làm là lấy các giới hạn của lớp, nhân chiều rộng và chiều cao của giới hạn với các giá trị chuẩn hóa cũ và mới của anchorPoint, lấy sự khác biệt của hai anchorPoints và áp dụng sự khác biệt đó cho vị trí của lớp.

Bạn thậm chí có thể tính toán xoay vòng theo cách này bằng cách sử dụng CGPointApplyAffineTransform()với CGAffineTransform của UIView.


4
Xem hàm ví dụ từ Magnus để xem câu trả lời của Brad có thể được thực hiện như thế nào trong Objective-C.
Jim Jeffers

Cảm ơn nhưng tôi không hiểu làm thế nào anchorPointpositioncó liên quan với nhau?
dumbfingers

6
@ ss1271 - Như tôi mô tả ở trên, anchorPoint của một lớp là cơ sở của hệ tọa độ của nó. Nếu nó được đặt thành (0,5, 0,5), thì khi bạn đặt vị trí cho một lớp, bạn đang đặt vị trí trung tâm của nó sẽ nằm trong hệ tọa độ của siêu lớp. Nếu bạn đặt anchorPoint thành (0.0, 0.5), đặt vị trí sẽ đặt vị trí trung tâm của cạnh trái của nó. Một lần nữa, Apple có một số hình ảnh đẹp trong tài liệu được liên kết ở trên (mà tôi đã cập nhật tài liệu tham khảo).
Brad Larson

Tôi đã sử dụng phương pháp được cung cấp bởi Magnus. Khi tôi NSLog vị trí và khung họ trông chính xác nhưng quan điểm của tôi vẫn bị di chuyển. Bất cứ ý tưởng tại sao? Nó giống như vị trí mới không được tính đến.
Alexis

Trong trường hợp của tôi, tôi muốn thêm một hình ảnh động trên lớp bằng phương thức video của lớp AVVideoCysisCoreAnimationTool của videoCysisCoreAnimationToolWithPostProcessingAsVideoLayer. Điều gì xảy ra là, một nửa lớp của tôi tiếp tục bị biến mất trong khi tôi xoay nó quanh trục y.
nr5

205

Tôi đã từng gặp vấn đề tương tự. Giải pháp của Brad Larson hoạt động rất tốt ngay cả khi tầm nhìn được xoay. Đây là giải pháp của ông được dịch thành mã.

-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view
{
    CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, 
                                   view.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, 
                                   view.bounds.size.height * view.layer.anchorPoint.y);

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);

    CGPoint position = view.layer.position;

    position.x -= oldPoint.x;
    position.x += newPoint.x;

    position.y -= oldPoint.y;
    position.y += newPoint.y;

    view.layer.position = position;
    view.layer.anchorPoint = anchorPoint;
}

Và tương đương nhanh chóng:

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

Chuyển 4.x

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x,
                           y: view.bounds.size.height * anchorPoint.y)


    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x,
                           y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

2
Hoàn hảo và hoàn toàn có ý nghĩa khi tôi có thể xem ví dụ của bạn ở đây. Cảm ơn vì điều đó!
Jim Jeffers

2
Tôi sử dụng mã này trong các phương thức awakeFromNib / initWithFrame của mình, nhưng nó vẫn không hoạt động, tôi có cần cập nhật màn hình không? chỉnh sửa: setNeedDisplay không hoạt động
Adam Carter

2
Tại sao bạn sử dụng view.bound? Bạn có nên sử dụng layer.bound không?
zakdances

1
trong trường hợp của tôi, cài đặt giá trị thành: view.layer.poseition không thay đổi bất cứ điều gì
AndrewK

5
FWIW, tôi đã phải bọc phương thức setAnchor trong một khối Clark_async để nó hoạt động. Không chắc tại sao khi tôi gọi nó trong viewDidLoad. Là viewDidLoad không còn trên hàng đợi luồng chính?
John Fowler

44

Chìa khóa để giải quyết điều này là sử dụng thuộc tính khung, điều kỳ lạ là điều duy nhất thay đổi.

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(1, 1)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 1, y: 1)
self.frame = oldFrame

Sau đó, tôi thực hiện thay đổi kích thước của mình, nơi nó chia tỷ lệ từ anchorPoint. Sau đó, tôi phải khôi phục lại anchorPoint cũ;

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(0.5,0.5)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.frame = oldFrame

EDIT: điều này sẽ xuất hiện nếu chế độ xem được xoay, vì thuộc tính khung không được xác định nếu CGAffineTransform đã được áp dụng.


9
Chỉ muốn thêm lý do tại sao điều này hoạt động và dường như là cách dễ nhất: theo tài liệu, thuộc tính khung là thuộc tính "sáng tác": "Giá trị của khung được lấy từ các thuộc tính giới hạn, neo và vị trí." Đó cũng là lý do tại sao thuộc tính "khung" không thể hoạt hình.
DarkDust

1
Đây là cách tiếp cận ưa thích của tôi. Nếu bạn không cố gắng điều khiển các giới hạn và vị trí một cách độc lập hoặc hoạt hình một trong hai, chỉ cần chụp khung hình trước khi bạn thay đổi điểm neo thường là đủ. Không cần toán.
CIFilter

28

Đối với tôi hiểu positionanchorPointdễ nhất khi tôi bắt đầu so sánh nó với sự hiểu biết của tôi về frame.origin trong UIView. Một UIView có frame.origin = (20,30) có nghĩa là UIView cách 20 điểm từ trái và 30 điểm từ trên cùng của chế độ xem chính. Khoảng cách này được tính từ điểm nào của một UIView? Nó được tính từ góc trên bên trái của một UIView.

Trong lớp anchorPointđánh dấu điểm (ở dạng chuẩn hóa tức là 0 đến 1) từ đó khoảng cách này được tính toán, ví dụ như layer.poseition = (20, 30) có nghĩa là lớp đó cách anchorPoint20 điểm từ trái và 30 điểm từ trên cùng của lớp cha. Theo mặc định, một lớp neo là (0,5, 0,5) vì vậy điểm tính toán khoảng cách nằm ngay chính giữa của lớp. Hình dưới đây sẽ giúp làm rõ quan điểm của tôi:

nhập mô tả hình ảnh ở đây

anchorPoint cũng là điểm xoay quanh việc xoay vòng sẽ xảy ra trong trường hợp bạn áp dụng một biến đổi cho lớp.


1
Xin vui lòng cho tôi biết làm thế nào tôi có thể giải quyết vấn đề nhảy điểm neo này, người mà UIView được thiết lập bằng cách sử dụng bố cục tự động, có nghĩa là với bố cục tự động, chúng tôi không giả sử có được hoặc đặt các thuộc tính khung và giới hạn.
SJ

17

Có một giải pháp đơn giản như vậy. Điều này dựa trên câu trả lời của Kenny. Nhưng thay vì áp dụng khung cũ, hãy sử dụng nguồn gốc của nó và khung mới để tính toán bản dịch, sau đó áp dụng bản dịch đó cho trung tâm. Nó hoạt động với góc nhìn xoay quá! Đây là mã, đơn giản hơn nhiều so với các giải pháp khác:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
   let oldOrigin = view.frame.origin
   view.layer.anchorPoint = anchorPoint
   let newOrigin = view.frame.origin

   let translation = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)

   view.center = CGPoint(x: view.center.x - translation.x, y: view.center.y - translation.y)
}

2
Phương pháp nhỏ xinh ... hoạt động tốt với một vài sửa chữa nhỏ: Dòng 3: viết hoa P trong anchorPoint. Dòng 8: TRANS.Y = MỚI - TUỔI
bob

2
tôi vẫn còn bối rối làm thế nào tôi có thể sử dụng nó. Tôi đang phải đối mặt với cùng một vấn đề ngay bây giờ để xoay chuyển quan điểm của tôi với uigesturotation. Tôi lạ là tôi nên gọi phương pháp này ở đâu. Nếu tôi đang gọi trong xử lý nhận dạng cử chỉ. Nó làm cho tầm nhìn biến mất.
JAck

3
Câu trả lời này thật ngọt ngào bây giờ tôi bị tiểu đường.
GeneCode

14

Đối với những người cần nó, đây là giải pháp của Magnus trong Swift:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
    var newPoint: CGPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint: CGPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position: CGPoint = view.layer.position

    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.setTranslatesAutoresizingMaskIntoConstraints(true)     // Added to deal with auto layout constraints
    view.layer.anchorPoint = anchorPoint
    view.layer.position = position
}

1
upvote cho view.setTranslatesAutoresizingMaskIntoConstraints(true). cảm ơn người đàn ông
Oscar Yuandinata

1
view.setTranslatesAutoresizingMaskIntoConstraints(true)là cần thiết khi sử dụng các ràng buộc bố trí tự động.
SamirChen

1
Cám ơn bạn rất nhiều về điều này. Các translatesAutoresizingMaskIntoConstraintschỉ là những gì tôi đã mất tích, có thể đã học được bây giờ
Žiga

5

Dưới đây là câu trả lời của user945711 được điều chỉnh cho NSView trên OS X. Bên cạnh NSView không có thuộc .centertính, khung của NSView không thay đổi (có thể do NSView không đi kèm với CALayer theo mặc định) nhưng nguồn gốc khung CALayer thay đổi khi thay đổi điểm neo.

func setAnchorPoint(anchorPoint: NSPoint, view: NSView) {
    guard let layer = view.layer else { return }

    let oldOrigin = layer.frame.origin
    layer.anchorPoint = anchorPoint
    let newOrigin = layer.frame.origin

    let transition = NSMakePoint(newOrigin.x - oldOrigin.x, newOrigin.y - oldOrigin.y)
    layer.frame.origin = NSMakePoint(layer.frame.origin.x - transition.x, layer.frame.origin.y - transition.y)
}

4

Nếu bạn thay đổi anchorPoint, vị trí của nó cũng sẽ thay đổi, KHÔNG GIỚI HẠN bạn là điểm gốc CGPointZero.

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;

1
Bạn không thể so sánh vị trí và neo Điểm, thang điểm neo từ 0 đến 1
Edward Anthony

4

Chỉnh sửa và xem Điểm neo của UIView ngay trên Storyboard (Swift 3)

Đây là một giải pháp thay thế cho phép bạn thay đổi điểm neo thông qua Trình kiểm tra thuộc tính và có một thuộc tính khác để xem điểm neo để xác nhận.

Tạo tập tin mới để đưa vào dự án của bạn

import UIKit

@IBDesignable
class UIViewAnchorPoint: UIView {

    @IBInspectable var showAnchorPoint: Bool = false
    @IBInspectable var anchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5) {
        didSet {
            setAnchorPoint(anchorPoint: anchorPoint)
        }
    }

    override func draw(_ rect: CGRect) {
        if showAnchorPoint {
            let anchorPointlayer = CALayer()
            anchorPointlayer.backgroundColor = UIColor.red.cgColor
            anchorPointlayer.bounds = CGRect(x: 0, y: 0, width: 6, height: 6)
            anchorPointlayer.cornerRadius = 3

            let anchor = layer.anchorPoint
            let size = layer.bounds.size

            anchorPointlayer.position = CGPoint(x: anchor.x * size.width, y: anchor.y * size.height)
            layer.addSublayer(anchorPointlayer)
        }
    }

    func setAnchorPoint(anchorPoint: CGPoint) {
        var newPoint = CGPoint(x: bounds.size.width * anchorPoint.x, y: bounds.size.height * anchorPoint.y)
        var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y)

        newPoint = newPoint.applying(transform)
        oldPoint = oldPoint.applying(transform)

        var position = layer.position
        position.x -= oldPoint.x
        position.x += newPoint.x

        position.y -= oldPoint.y
        position.y += newPoint.y

        layer.position = position
        layer.anchorPoint = anchorPoint
    }
}

Thêm Chế độ xem vào Bảng phân cảnh và đặt Lớp tùy chỉnh

Lớp tùy chỉnh

Bây giờ, đặt Điểm neo mới cho UIView

Trình diễn

Bật Show Anchor Point sẽ hiển thị một chấm đỏ để bạn có thể thấy rõ hơn điểm neo sẽ được nhìn thấy ở đâu. Bạn luôn có thể tắt nó sau.

Điều này thực sự giúp tôi khi lập kế hoạch chuyển đổi trên UIViews.


Không có dấu chấm nào trên UIView, tôi đã sử dụng Objective-C với Swift, đính kèm lớp tùy chỉnh UIView từ mã của bạn và bật Show neo ... nhưng không có dấu chấm nào
Genevios

1

Dành cho Swift 3:

func setAnchorPoint(_ anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, y: view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

1
Cảm ơn bạn vì điều này :) Tôi vừa dán nó vào dự án của mình dưới dạng func tĩnh và hoạt động như một lá bùa: D
Kjell

0

Mở rộng câu trả lời tuyệt vời và kỹ lưỡng của Magnus, tôi đã tạo một phiên bản hoạt động trên các lớp phụ:

-(void)setAnchorPoint:(CGPoint)anchorPoint forLayer:(CALayer *)layer
{
    CGPoint newPoint = CGPointMake(layer.bounds.size.width * anchorPoint.x, layer.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(layer.bounds.size.width * layer.anchorPoint.x, layer.bounds.size.height * layer.anchorPoint.y);
    CGPoint position = layer.position;
    position.x -= oldPoint.x;
    position.x += newPoint.x;
    position.y -= oldPoint.y;
    position.y += newPoint.y;
    layer.position = position;
    layer.anchorPoint = anchorPoint;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.