การเปลี่ยนจุดยึด CALayer ของฉันจะย้ายมุมมอง


132

ฉันต้องการแก้ไขanchorPointแต่ให้มุมมองอยู่ที่เดิม ฉันได้พยายามNSLogไอเอ็นจีself.layer.positionและself.centerและพวกเขาทั้งสองอยู่เหมือนกันโดยไม่คำนึงถึงการเปลี่ยนแปลง Anchorpoint มุมมองของฉันยังเคลื่อนไหว!

มีเคล็ดลับในการทำอย่างไร

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

ผลลัพธ์คือ:

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

คำตอบ:


140

ส่วนLayer Geometry และ Transformsของ Core Animation Programming Guide จะอธิบายถึงความสัมพันธ์ระหว่างตำแหน่งของ CALayer และคุณสมบัติ anchorPoint โดยทั่วไปตำแหน่งของเลเยอร์จะถูกระบุในแง่ของตำแหน่งของจุดยึดของเลเยอร์ โดยค่าเริ่มต้นจุดยึดของเลเยอร์คือ (0.5, 0.5) ซึ่งอยู่ตรงกลางของเลเยอร์ เมื่อคุณกำหนดตำแหน่งของเลเยอร์คุณจะตั้งค่าตำแหน่งของกึ่งกลางของเลเยอร์ในระบบพิกัดของเลเยอร์ชั้นบน

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

คุณอาจสามารถพิจารณาการหมุนเวียนด้วยวิธีนี้ได้โดยใช้CGPointApplyAffineTransform()กับ CGAffineTransform ของ UIView


4
ดูตัวอย่างฟังก์ชันจาก Magnus เพื่อดูว่าคำตอบของ Brad สามารถนำไปใช้ใน Objective-C ได้อย่างไร
Jim Jeffers

ขอบคุณ แต่ฉันไม่เข้าใจว่าanchorPointและpositionเกี่ยวข้องกันอย่างไร?
dumbfingers

6
@ ss1271 - ตามที่ฉันอธิบายไว้ข้างต้นจุดยึดของเลเยอร์เป็นพื้นฐานของระบบพิกัด หากตั้งค่าเป็น (0.5, 0.5) จากนั้นเมื่อคุณกำหนดตำแหน่งสำหรับเลเยอร์คุณกำลังตั้งค่าที่ศูนย์กลางของมันจะอยู่ในระบบพิกัดของซูเปอร์เลเยอร์ หากคุณตั้งค่าจุดยึดเป็น (0.0, 0.5) การตั้งค่าตำแหน่งจะกำหนดจุดกึ่งกลางของขอบด้านซ้าย อีกครั้ง Apple มีภาพที่สวยงามในเอกสารที่เชื่อมโยงด้านบน (ซึ่งฉันได้อัปเดตข้อมูลอ้างอิงถึง)
Brad Larson

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

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

205

ผมมีปัญหาเหมือนกัน. โซลูชันของ Brad Larson ใช้งานได้ดีแม้ว่าจะหมุนมุมมอง นี่คือโซลูชันของเขาที่แปลเป็นรหัส

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

และเทียบเท่าที่รวดเร็ว:

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
}

SWIFT 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
สมบูรณ์แบบและสมเหตุสมผลเมื่อฉันได้เห็นตัวอย่างของคุณที่นี่ ขอบคุณสำหรับสิ่งนี้!
Jim Jeffers

2
ฉันใช้รหัสนี้ในวิธีการ AwakenFromNib / initWithFrame ของฉัน แต่ยังใช้ไม่ได้ฉันต้องอัปเดตการแสดงผลหรือไม่ แก้ไข: setNeedsDisplay ไม่ทำงาน
Adam Carter

2
ทำไมคุณถึงใช้ view.bounds คุณไม่ควรใช้ Layer.bounds?
zakdances

1
ในกรณีของฉันการตั้งค่าค่าเป็น: view.layer.position ไม่เปลี่ยนแปลงอะไรเลย
AndrewK

5
FWIW ฉันต้องห่อเมธอด setAnchor ในบล็อก dispatch_async เพื่อให้มันทำงาน ไม่แน่ใจว่าทำไมฉันถึงเรียกมันว่าภายใน viewDidLoad viewDidLoad ไม่อยู่ในคิวเธรดหลักอีกต่อไปหรือไม่
John Fowler

46

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

สวิฟต์ 2

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

สวิฟต์ 3

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

จากนั้นฉันจะทำการปรับขนาดโดยที่จะปรับขนาดจากจุดยึด จากนั้นฉันต้องคืนค่าจุดยึดเก่า

สวิฟต์ 2

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

สวิฟต์ 3

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

แก้ไข: สิ่งนี้จะหลุดออกหากมุมมองถูกหมุนเนื่องจากคุณสมบัติของเฟรมไม่ได้กำหนดไว้หากมีการใช้ CGAffineTransform


10
เพียงแค่ต้องการเพิ่มว่าเหตุใดจึงใช้งานได้และดูเหมือนจะเป็นวิธีที่ง่ายที่สุด: ตามเอกสารคุณสมบัติของเฟรมคือคุณสมบัติ "ประกอบ": "ค่าของเฟรมมาจากคุณสมบัติขอบเขตจุดยึดและตำแหน่ง" นั่นเป็นสาเหตุที่คุณสมบัติ "เฟรม" ไม่เคลื่อนไหว
DarkDust

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

28

สำหรับฉันเข้าใจpositionและanchorPointง่ายที่สุดเมื่อฉันเริ่มเปรียบเทียบกับความเข้าใจเกี่ยวกับ frame.origin ใน UIView UIView พร้อม frame.origin = (20,30) หมายความว่า UIView อยู่ที่ 20 จุดจากด้านซ้ายและ 30 คะแนนจากด้านบนของมุมมองระดับบนสุด ระยะทางนี้คำนวณจากจุดใดของ UIView? คำนวณจากมุมบนซ้ายของ UIView

ในเลเยอร์ให้anchorPointทำเครื่องหมายจุด (ในรูปแบบปกติเช่น 0 ถึง 1) จากจุดที่คำนวณระยะทางนี้เช่นเลเยอร์ตำแหน่ง = (20, 30) หมายความว่าเลเยอร์anchorPointมี 20 จุดจากด้านซ้ายและ 30 พอยต์จากด้านบนของเลเยอร์พาเรนต์ โดยค่าเริ่มต้นจุดยึดของเลเยอร์คือ (0.5, 0.5) ดังนั้นจุดคำนวณระยะทางจึงอยู่ตรงกลางของเลเยอร์ รูปต่อไปนี้จะช่วยชี้แจงประเด็นของฉัน:

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

anchorPoint ยังเป็นจุดรอบ ๆ ที่การหมุนจะเกิดขึ้นในกรณีที่คุณใช้การแปลงกับเลเยอร์


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

17

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

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
วิธีการเล็ก ๆ น้อย ๆ ที่ดี ... ใช้ได้ผลกับการแก้ไขเล็กน้อย: บรรทัดที่ 3: capital P ในจุดยึด บรรทัดที่ 8: TRANS.Y = ใหม่ - เก่า
บ๊อบ

2
ฉันยังสับสนฉันจะใช้มันได้อย่างไร ตอนนี้ฉันกำลังประสบปัญหาเดียวกันกับการหมุนมุมมองด้วย uigesturerotation ฉันแปลกที่ฉันควรเรียกวิธีนี้ว่าที่ไหน หากฉันกำลังโทรด้วยตัวจัดการการจดจำท่าทาง มันทำให้มุมมองหายไป
JAck

3
คำตอบนี้หวานมากตอนนี้ฉันเป็นโรคเบาหวาน
GeneCode

15

สำหรับผู้ที่ต้องการนี่คือโซลูชันของ Magnus ใน 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 view.setTranslatesAutoresizingMaskIntoConstraints(true)สำหรับ ขอบคุณผู้ชาย
Oscar Yuandinata

1
view.setTranslatesAutoresizingMaskIntoConstraints(true)เป็นสิ่งจำเป็นเมื่อใช้ข้อ จำกัด การจัดวางอัตโนมัติ
SamirChen

1
ขอบคุณมากสำหรับเรื่องนี้! นี่translatesAutoresizingMaskIntoConstraintsเป็นเพียงสิ่งที่ฉันขาดหายไปสามารถเรียนรู้ได้ในตอนนี้
Ziga

5

นี่คือคำตอบของ user945711 ที่ปรับสำหรับ NSView บน OS X นอกจาก NSView จะไม่มี.centerคุณสมบัติแล้วเฟรมของ NSView จะไม่เปลี่ยนแปลง (อาจเป็นเพราะ NSViews ไม่ได้มาพร้อมกับ CALayer โดยค่าเริ่มต้น) แต่จุดเริ่มต้นของเฟรม CALayer จะเปลี่ยนไปเมื่อจุดยึดมีการเปลี่ยนแปลง

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

ถ้าคุณเปลี่ยน Anchorpoint CGPointZeroตำแหน่งจะมีการเปลี่ยนแปลงมากเกินไปเว้นแต่คุณจะกำเนิดเป็นจุดศูนย์

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

1
คุณไม่สามารถเปรียบเทียบตำแหน่งและจุดยึดจุดยึดสมอสเกลระหว่าง 0 ถึง 1.0
Edward Anthony

4

แก้ไขและดูจุดยึดของ UIView บนกระดานเรื่องราว (Swift 3)

นี่เป็นทางเลือกอื่นที่ช่วยให้คุณสามารถเปลี่ยนจุดยึดผ่าน Attributes Inspector และมีคุณสมบัติอื่นเพื่อดูจุดยึดสำหรับการยืนยัน

สร้างไฟล์ใหม่เพื่อรวมไว้ในโครงการของคุณ

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

เพิ่ม View to Storyboard และตั้งค่า Custom Class

คลาสที่กำหนดเอง

ตั้งค่าจุดยึดใหม่สำหรับ UIView แล้ว

สาธิต

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

สิ่งนี้ช่วยฉันได้มากเมื่อวางแผนการเปลี่ยนแปลงใน UIViews


ไม่มีใครสนใจ UIView ฉันใช้ Objective-C กับ Swift แนบคลาส UIView ที่กำหนดเองจากโค้ดของคุณและเปิด Show anch ... แต่ไม่มีใครจุด
Genevios

1

สำหรับ 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
ขอบคุณสำหรับสิ่งนี้ :) ฉันเพิ่งวางลงในโครงการของฉันเป็น func แบบคงที่และทำงานได้อย่างมีเสน่ห์: D
Kjell

0

เมื่อขยายคำตอบที่ยอดเยี่ยมและละเอียดถี่ถ้วนของ Magnus ฉันได้สร้างเวอร์ชันที่ใช้งานได้กับเลเยอร์ย่อย:

-(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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.