ทำความเข้าใจกับ convertRect: toView:, convertRect: FromView :, convertPoint: toView: และ convertPoint: fromView: methods


128

ฉันพยายามเข้าใจฟังก์ชันของวิธีการเหล่านี้ คุณช่วยให้เราใช้งานง่าย ๆ เพื่อทำความเข้าใจความหมายของพวกเขาได้หรือไม่?

จากเอกสารตัวอย่างเช่นconvertPoint: fromView: method ถูกอธิบายดังนี้:

แปลงจุดจากระบบพิกัดของมุมมองที่กำหนดเป็นของผู้รับ

อะไรระบบพิกัดเฉลี่ย? แล้วผู้รับล่ะ

ตัวอย่างเช่นมันเหมาะสมหรือไม่เมื่อใช้convertPoint: fromView:ชอบสิ่งต่อไปนี้หรือไม่

CGPoint p = [view1 convertPoint:view1.center fromView:view1];

ใช้ยูทิลิตี้ NSLog ฉันได้ตรวจสอบว่าค่า p สอดคล้องกับศูนย์กลางของ view1

ขอบคุณล่วงหน้า.

แก้ไข:สำหรับผู้ที่สนใจฉันได้สร้างข้อมูลโค้ดง่ายๆเพื่อทำความเข้าใจกับวิธีการเหล่านี้

UIView* view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 200)];
view1.backgroundColor = [UIColor redColor];

NSLog(@"view1 frame: %@", NSStringFromCGRect(view1.frame));        
NSLog(@"view1 center: %@", NSStringFromCGPoint(view1.center));   

CGPoint originInWindowCoordinates = [self.window convertPoint:view1.bounds.origin fromView:view1];        
NSLog(@"convertPoint:fromView: %@", NSStringFromCGPoint(originInWindowCoordinates));

CGPoint originInView1Coordinates = [self.window convertPoint:view1.frame.origin toView:view1];        
NSLog(@"convertPoint:toView: %@", NSStringFromCGPoint(originInView1Coordinates));

ในทั้งสองกรณี self.window เป็นผู้รับ แต่มีความแตกต่าง ในกรณีแรกพารามิเตอร์ convertPoint จะแสดงในพิกัด view1 ผลลัพธ์มีดังต่อไปนี้:

convertPoint: จากมุมมอง: {100, 100}

ในอันที่สองแทน convertPoint จะแสดงในพิกัด superview (self.window) ผลลัพธ์มีดังต่อไปนี้:

convertPoint: toView: {0, 0}

คำตอบ:


184

แต่ละมุมมองมีระบบพิกัดของตัวเอง - มีจุดเริ่มต้นที่ 0,0 และความกว้างและความสูง นี่คือคำอธิบายในboundsสี่เหลี่ยมผืนผ้าของมุมมอง frameในมุมมองของ แต่จะมีต้นกำเนิดที่จุดภายในขอบเขตสี่เหลี่ยมของ SuperView ของมัน

มุมมองด้านนอกสุดของลำดับชั้นการดูของคุณมีต้นกำเนิดอยู่ที่ 0,0 ซึ่งสอดคล้องกับด้านบนซ้ายของหน้าจอใน iOS

หากคุณเพิ่มมุมมองย่อยที่ 20,30 ในมุมมองนี้ดังนั้นจุดที่ 0,0 ในมุมมองย่อยจะสอดคล้องกับจุดที่ 20,30 ในมุมมองย่อย การแปลงนี้เป็นวิธีการที่ทำ

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

CGPoint originInSuperview = [superview convertPoint:CGPointZero fromView:subview];

"การรับ" เป็นมาตรฐานระยะวัตถุประสงค์ -c สำหรับวัตถุที่ได้รับข้อความ (วิธีนี้ยังเป็นที่รู้จักกันเป็นข้อความ) superviewดังนั้นในตัวอย่างของฉันที่นี่รับคือ


3
ขอบคุณสำหรับการตอบกลับของคุณ jrturton คำอธิบายที่มีประโยชน์มาก convertPointและconvertRectแตกต่างกันในประเภทผลตอบแทน หรือCGPoint CGRectแต่สิ่งที่เกี่ยวfromและto? มีกฎง่ายๆที่ฉันสามารถใช้? ขอบคุณ.
Lorenzo B

จากเมื่อคุณต้องการแปลงจากถึงเมื่อคุณต้องการแปลงเป็น?
jrturton

3
เกิดอะไรขึ้นถ้า superview ไม่ใช่มุมมองพาเรนต์ย่อยของมุมมองย่อยโดยตรงมันจะยังใช้งานได้หรือไม่
Van Du Tran

3
@VanDuTran ใช่ตราบใดที่พวกเขาอยู่ในหน้าต่างเดียวกัน (ซึ่งมุมมองส่วนใหญ่ในแอป iOS เป็น)
jrturton

คำตอบที่ดี ช่วยเคลียร์มากสำหรับฉัน อ่านเพิ่มเติมใด ๆ
JaeGeeTee

39

ฉันมักจะพบความสับสนนี้ดังนั้นฉันจึงสร้างสนามเด็กเล่นที่คุณสามารถสำรวจสิ่งที่convertฟังก์ชั่นทำ สิ่งนี้ทำใน Swift 3 และ Xcode 8.1b:

import UIKit
import PlaygroundSupport

class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Main view
        view.backgroundColor = .black
        view.frame = CGRect(x: 0, y: 0, width: 500, height: 500)

        // Red view
        let redView = UIView(frame: CGRect(x: 20, y: 20, width: 460, height: 460))
        redView.backgroundColor = .red
        view.addSubview(redView)

        // Blue view
        let blueView = UIView(frame: CGRect(x: 20, y: 20, width: 420, height: 420))
        blueView.backgroundColor = .blue
        redView.addSubview(blueView)

        // Orange view
        let orangeView = UIView(frame: CGRect(x: 20, y: 20, width: 380, height: 380))
        orangeView.backgroundColor = .orange
        blueView.addSubview(orangeView)

        // Yellow view
        let yellowView = UIView(frame: CGRect(x: 20, y: 20, width: 340, height: 100))
        yellowView.backgroundColor = .yellow
        orangeView.addSubview(yellowView)


        // Let's try to convert now
        var resultFrame = CGRect.zero
        let randomRect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 50)

        /*
        func convert(CGRect, from: UIView?)
        Converts a rectangle from the coordinate system of another view to that of the receiver.
        */

        // The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view:
        resultFrame = view.convert(randomRect, from: yellowView)

        // Try also one of the following to get a feeling of how it works:
        // resultFrame = view.convert(randomRect, from: orangeView)
        // resultFrame = view.convert(randomRect, from: redView)
        // resultFrame = view.convert(randomRect, from: nil)

        /*
        func convert(CGRect, to: UIView?)
        Converts a rectangle from the receiver’s coordinate system to that of another view.
        */

        // The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view
        resultFrame = yellowView.convert(randomRect, to: view)
        // Same as what we did above, using "from:"
        // resultFrame = view.convert(randomRect, from: yellowView)

        // Also try:
        // resultFrame = orangeView.convert(randomRect, to: view)
        // resultFrame = redView.convert(randomRect, to: view)
        // resultFrame = orangeView.convert(randomRect, to: nil)


        // Add an overlay with the calculated frame to self.view
        let overlay = UIView(frame: resultFrame)
        overlay.backgroundColor = UIColor(white: 1.0, alpha: 0.9)
        overlay.layer.borderColor = UIColor.black.cgColor
        overlay.layer.borderWidth = 1.0
        view.addSubview(overlay)
    }
}

var ctrl = MyViewController()
PlaygroundPage.current.liveView = ctrl.view

อย่าลืมแสดงผู้ช่วยแก้ไข ( ) เพื่อดูมุมมองมันควรมีลักษณะดังนี้:

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

รู้สึกอิสระที่จะมีส่วนร่วมตัวอย่างเพิ่มเติมที่นี่หรือในส่วนสำคัญนี้


ขอบคุณนี่เป็นประโยชน์อย่างมากในการทำความเข้าใจการแปลง
อานันท์

ภาพรวมที่ดี อย่างไรก็ตามฉันคิดว่าสิ่งเหล่าself.viewนี้ซ้ำซ้อนใช้งานง่ายviewหากselfไม่จำเป็น
Jakub Truhlář

ขอบคุณ @ JakubTruhlářฉันเพิ่งลบself
phi

ขอบคุณมาก! สนามเด็กเล่นของคุณช่วยฉันได้มากในการรู้ว่าการแปลงนี้ทำงานอย่างไร
Anvar Azizov

30

นี่คือคำอธิบายเป็นภาษาอังกฤษธรรมดา

เมื่อคุณต้องการแปลง rect ของ subview ( aViewเป็น subview ของ[aView superview]) เป็นพื้นที่ประสานงานของมุมมองอื่น ( self)

// So here I want to take some subview and put it in my view's coordinate space
_originalFrame = [[aView superview] convertRect: aView.frame toView: self];

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

แก้ไข. มันไม่ย้ายมุมมอง วิธีการส่งกลับ CGRect สิ่งที่คุณเลือกทำกับ CGRect นั้นคือธุรกิจของคุณ :-) ในกรณีข้างต้นมันสามารถใช้เพื่อย้ายมุมมองหนึ่งไปยังลำดับชั้นของอีกมุมมองหนึ่งในขณะที่รักษาตำแหน่งที่มองเห็นบนหน้าจอ
horseshoe7 7

14

ทุกมุมมองใน iOS มีระบบพิกัด ระบบพิกัดเหมือนกราฟซึ่งมีแกน x (เส้นแนวนอน) และแกน y (เส้นแนวตั้ง) จุดที่เส้นตัดกันเรียกว่าจุดกำเนิด จุดจะถูกแทนด้วย (x, y) ตัวอย่างเช่น (2, 1) หมายความว่าจุดนั้นเหลือ 2 พิกเซลและลดลง 1 พิกเซล

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับระบบพิกัดได้ที่นี่ - http://en.wikipedia.org/wiki/Coordinate_system

แต่สิ่งที่คุณต้องรู้ก็คือใน iOS ทุก ๆ มุมมองมีระบบพิกัดของตัวเองที่มุมซ้ายบนเป็นจุดกำเนิด แกน X เพิ่มขึ้นเรื่อย ๆ ไปทางขวาและแกน y เพิ่มขึ้นเรื่อย ๆ

สำหรับคำถามเกี่ยวกับคะแนนการแปลงให้ทำตัวอย่างนี้

มีมุมมองที่เรียกว่า V1 ซึ่งกว้าง 100 พิกเซลและสูง 100 พิกเซล ตอนนี้ภายในนั้นมีมุมมองอื่นที่เรียกว่า V2 ที่ (10, 10, 50, 50) ซึ่งหมายความว่า (10, 10) เป็นจุดในระบบพิกัดของ V1 ซึ่งควรจะอยู่มุมซ้ายบนของ V2 และ ( 50, 50) คือความกว้างและความสูงของ V2 ทีนี้ลองใช้ระบบพิกัดของ INSIDE V2 พูด (20, 20) ทีนี้ประเด็นนั้นจะอยู่ในระบบพิกัดของ V1 อย่างไร นั่นคือวิธีการที่ใช้ (แน่นอนคุณสามารถคำนวณเองได้ แต่มันช่วยให้คุณประหยัดมากขึ้น) สำหรับบันทึกแล้วจุดใน V1 จะเป็น (30, 30)

หวังว่านี่จะช่วยได้


9

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

ตัวควบคุมมุมมองของฉันมีมุมมองปกติ

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

ภายในหนึ่งในมุมมองการจัดกลุ่มเหล่านั้นฉันมีปุ่มเพิ่มที่แสดงตัวควบคุมมุมมองป๊อปโอเวอร์ที่ผู้ใช้ป้อนข้อมูลบางอย่าง

view
--groupingView
----addButton

ระหว่างการหมุนอุปกรณ์ตัวควบคุมมุมมองจะได้รับการแจ้งเตือนผ่านการเรียก UIPopoverViewControllerDelegate popoverController: willRepositionPopoverToRect: inView:

- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
{
    *rect = [self.addButton convertRect:self.addbutton.bounds toView:*view];
}

ส่วนสำคัญที่มาจากคำอธิบายที่ได้รับจากสองคำตอบแรกข้างต้นคือ rect ที่ฉันต้องการแปลงจากเป็นขอบเขตของปุ่มเพิ่มไม่ใช่เฟรม

ฉันไม่ได้ลองสิ่งนี้ด้วยลำดับชั้นมุมมองที่ซับซ้อนมากขึ้น แต่ฉันสงสัยว่าโดยใช้มุมมองที่ให้ไว้ในการเรียกใช้เมธอด (inView :) เราได้รับผลกระทบจากความยุ่งเหยิงของมุมมองใบไม้แบบหลายชั้น


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

3

ฉันใช้โพสต์นี้เพื่อสมัครในกรณีของฉัน หวังว่านี่จะช่วยผู้อ่านอีกคนในอนาคต

มุมมองสามารถดูได้เฉพาะลูก ๆ และมุมมองพาเรนต์เท่านั้น ไม่เห็นผู้ปกครองที่ยิ่งใหญ่หรือมุมมองหลาน

ดังนั้นในกรณีของฉันฉันมีมุมมองที่ปู่ย่าตายายเรียกว่าself.viewในการนี้self.viewผมได้เพิ่ม subviews เรียกว่า,self.child1OfView self.child2OfViewในself.child1OfViewฉันได้เพิ่มหัวข้อย่อยที่เรียกว่าself.child1OfView1, self.child2OfView1.
ทีนี้ถ้าฉันย้ายร่างกายไปself.child1OfView1ยังพื้นที่นอกขอบเขตของself.child1OfViewจุดอับละอองเกสรself.viewแล้วให้คำนวณตำแหน่งใหม่สำหรับself.child1OfView1ภายในself.view:

CGPoint newPoint = [self.view convertPoint:self.child1OfView1.center fromView:self.child1OfView];

3

คุณสามารถดูโค้ดด้านล่างเพื่อให้คุณสามารถเข้าใจว่ามันใช้งานได้จริง

    let scrollViewTemp = UIScrollView.init(frame: CGRect.init(x: 10, y: 10, width: deviceWidth - 20, height: deviceHeight - 20))

override func viewDidLoad() {
    super.viewDidLoad()

    scrollViewTemp.backgroundColor = UIColor.lightGray
    scrollViewTemp.contentSize = CGSize.init(width: 2000, height: 2000)
    self.view.addSubview(scrollViewTemp)

    let viewTemp = UIView.init(frame: CGRect.init(x: 100, y: 100, width: 150, height: 150))
    viewTemp.backgroundColor = UIColor.green
    self.view.addSubview(viewTemp)

    let viewSecond = UIView.init(frame: CGRect.init(x: 100, y: 700, width: 300, height: 300))
    viewSecond.backgroundColor = UIColor.red
    self.view.addSubview(viewSecond)

    self.view.convert(viewTemp.frame, from: scrollViewTemp)
    print(viewTemp.frame)


    /*  First take one point CGPoint(x: 10, y: 10) of viewTemp frame,then give distance from viewSecond frame to this point.
     */
    let point = viewSecond.convert(CGPoint(x: 10, y: 10), from: viewTemp)
    //output:   (10.0, -190.0)
    print(point)

    /*  First take one point CGPoint(x: 10, y: 10) of viewSecond frame,then give distance from viewTemp frame to this point.
     */
    let point1 = viewSecond.convert(CGPoint(x: 10, y: 10), to: viewTemp)
    //output:  (10.0, 210.0)
    print(point1)

    /*  First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewSecond frame,then give distance from viewTemp frame to this rect.
     */
    let rect1 = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), to: viewTemp)
    //output:  (10.0, 210.0, 20.0, 20.0)
    print(rect1)

    /* First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewTemp frame,then give distance from viewSecond frame to this rect.
     */
    let rect = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), from: viewTemp)
    //output:  (10.0, -190.0, 20.0, 20.0)
    print(rect)

}

1

ฉันอ่านคำตอบและเข้าใจกลไก แต่ฉันคิดว่าตัวอย่างสุดท้ายไม่ถูกต้อง ตามเอกสาร API, คุณสมบัติศูนย์กลางของมุมมองมีจุดศูนย์กลางที่รู้จักของมุมมองในระบบพิกัดของ superview

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

คุณสามารถทำได้สองวิธี (ทั้งสองควรให้ค่าเท่ากัน):

CGPoint centerInSubview = [subview convertPoint:subview.center fromView:subview.superview];

หรือ

CGPoint centerInSubview = [subview.superview convertPoint:subview.center toView:subview];

ฉันเข้าใจวิธีนี้ควรใช้หรือไม่


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

1

จุดสำคัญอีกประการหนึ่งเกี่ยวกับการใช้ API เหล่านี้ ตรวจสอบให้แน่ใจว่าเชนมุมมองพาเรนต์สมบูรณ์ระหว่าง rect ที่คุณแปลงและมุมมอง to / from ตัวอย่างเช่น - aView, bView และ cView -

  • aView เป็นมุมมองย่อยของ bView
  • เราต้องการแปลง aView.frame เป็น cView

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

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