Hiểu convertRect: toView:, convertRect: FromView:, convertPoint: toView: và convertPoint: fromView: phương thức


128

Tôi đang cố gắng để hiểu các chức năng của các phương pháp này. Bạn có thể cung cấp cho tôi một usecase đơn giản để hiểu ngữ nghĩa của họ không?

Từ tài liệu, ví dụ, convertPoint: fromView: phương thức được mô tả như sau:

Chuyển đổi một điểm từ hệ tọa độ của một khung nhìn đã cho sang góc nhìn của máy thu.

hệ toạ độ trung bình? Còn người nhận thì sao?

Ví dụ: nó có ý nghĩa khi sử dụng convertPoint: fromView: như sau không?

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

Sử dụng tiện ích NSLog, tôi đã xác minh rằng giá trị p trùng với trung tâm của view1.

Cảm ơn bạn trước.

EDIT: đối với những người quan tâm, tôi đã tạo một đoạn mã đơn giản để hiểu các phương thức này.

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

Trong cả hai trường hợp self.window là người nhận. Nhưng có một sự khác biệt. Trong trường hợp đầu tiên, tham số convertPoint được thể hiện theo tọa độ view1. Đầu ra là như sau:

convertPoint: fromView: {100, 100}

Trong phần thứ hai, thay vào đó, convertPoint được thể hiện theo tọa độ giám sát (self.window). Đầu ra là như sau:

convertPoint: toView: {0, 0}

Câu trả lời:


184

Mỗi chế độ xem có hệ tọa độ riêng - có gốc tọa độ 0,0 và chiều rộng và chiều cao. Điều này được mô tả trong boundshình chữ nhật của khung nhìn. Các framecác quan điểm, tuy nhiên, sẽ có nguồn gốc của nó ở điểm trong phạm vi hình chữ nhật của SuperView của nó.

Chế độ xem ngoài cùng của hệ thống phân cấp chế độ xem của bạn có nguồn gốc là 0,0 tương ứng với góc trên bên trái của màn hình trong iOS.

Nếu bạn thêm một khung nhìn phụ ở 20,30 vào khung nhìn này, thì một điểm tại 0,0 trong khung nhìn phụ tương ứng với một điểm ở 20,30 trong giám sát. Chuyển đổi này là những gì các phương pháp đang làm.

Ví dụ của bạn ở trên là vô nghĩa (không có ý định chơi chữ) vì nó chuyển đổi một điểm từ góc nhìn sang chính nó, vì vậy sẽ không có gì xảy ra. Bạn thường sẽ tìm ra vị trí của một góc nhìn nào đó liên quan đến giám sát của nó - để kiểm tra xem một chế độ xem có đang di chuyển khỏi màn hình hay không, ví dụ:

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

"Người nhận" là một thuật ngữ mục tiêu-c tiêu chuẩn cho đối tượng đang nhận tin nhắn (phương thức còn được gọi là tin nhắn) vì vậy trong ví dụ của tôi ở đây người nhận là superview.


3
Cảm ơn bạn đã trả lời của bạn, jrturton. Giải thích rất hữu ích. convertPointconvertRectkhác nhau trong loại trả lại. CGPointhoặc CGRect. Nhưng những gì về fromto? Có một quy tắc của ngón tay cái mà tôi có thể sử dụng? Cảm ơn bạn.
Lorenzo B

từ khi nào bạn muốn chuyển đổi từ, sang khi nào bạn muốn chuyển đổi sang?
jrturton

3
Điều gì sẽ xảy ra nếu giám sát không phải là quan điểm trực tiếp của phụ huynh về việc xem xét, nó vẫn hoạt động chứ?
Văn Du Trần

3
@VanDuTran có, miễn là chúng ở trong cùng một cửa sổ (mà hầu hết các lượt xem trong ứng dụng iOS là)
jrturton

Câu trả lời chính xác. Đã giúp làm sáng tỏ rất nhiều cho tôi. Bất kỳ đọc thêm?
JaeGeeTee

39

Tôi luôn thấy khó hiểu vì vậy tôi đã tạo ra một sân chơi nơi bạn có thể khám phá trực quan những gì convertchức năng làm. Điều này được thực hiện trong Swift 3 và 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

Hãy nhớ hiển thị Trình chỉnh sửa Trợ lý ( ) để xem các chế độ xem, nó sẽ giống như thế này:

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

Hãy đóng góp nhiều ví dụ ở đây hoặc trong ý chính này .


Cảm ơn bạn, điều này thực sự hữu ích trong việc hiểu chuyển đổi.
Anand

Tổng quan tuyệt vời. Tuy nhiên tôi nghĩ rằng đó self.viewlà những thứ dư thừa, sử dụng đơn giản viewnếu selfkhông cần thiết.
Jakub Truhlář 17/03/2017

Cảm ơn @ JakubTruhlář, tôi vừa xóaself
phi

Cảm ơn rất nhiều! Sân chơi của bạn đã giúp tôi rất nhiều để tìm ra cách chuyển đổi này hoạt động.
Anvar Azizov

30

Đây là một lời giải thích bằng tiếng Anh.

Khi bạn muốn chuyển đổi trực tiếp của một khung nhìn phụ (là một khung nhìn aViewphụ [aView superview]) sang không gian tọa độ của một khung nhìn khác ( 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
Điều này không đúng. Nó không di chuyển khung nhìn, nó chỉ cung cấp cho bạn tọa độ của khung nhìn theo quan điểm khác. Trong trường hợp của bạn, nó sẽ cung cấp cho bạn tọa độ của aView về mặt bản thân chúng ở đâu.
Carl

Chính xác. Nó không di chuyển tầm nhìn. Phương thức trả về một CGRect. Những gì bạn chọn để làm với CGRect đó là doanh nghiệp của bạn. :-) Trong trường hợp trên, nó có thể được sử dụng để di chuyển một chế độ xem sang phân cấp khác trong khi duy trì vị trí trực quan của nó trên màn hình.
móng

14

Mọi góc nhìn trong iOS đều có hệ thống tọa độ. Một hệ tọa độ giống như một đồ thị, có trục x (đường ngang) và trục y (đường dọc). Điểm tại đó các đường giao nhau được gọi là điểm gốc. Một điểm được đại diện bởi (x, y). Ví dụ: (2, 1) có nghĩa là điểm còn lại 2 pixel và giảm 1 pixel.

Bạn có thể đọc thêm về các hệ tọa độ tại đây - http://en.wikipedia.org/wiki/Coordinate_system

Nhưng điều bạn cần biết là, trong iOS, mọi chế độ xem đều có hệ thống tọa độ OWN, trong đó góc trên cùng bên trái là gốc. Trục X tiếp tục tăng sang phải và trục y tiếp tục tăng xuống.

Đối với câu hỏi điểm chuyển đổi, lấy ví dụ này.

Có một khung nhìn, được gọi là V1, rộng 100 pixel và cao 100 pixel. Bây giờ bên trong đó, có một góc nhìn khác, được gọi là V2, tại (10, 10, 50, 50), có nghĩa là (10, 10) là điểm trong hệ tọa độ của V1 nơi đặt góc trên cùng bên trái của V2 và ( 50, 50) là chiều rộng và chiều cao của V2. Bây giờ, hãy lấy một điểm hệ thống tọa độ của INSIDE V2, giả sử (20, 20). Bây giờ, điểm đó sẽ là gì trong hệ tọa độ của V1? Đó là những gì các phương thức dành cho (tất nhiên bạn có thể tự tính toán, nhưng chúng giúp bạn tiết kiệm thêm công việc). Đối với bản ghi, điểm trong V1 sẽ là (30, 30).

Hi vọng điêu nay co ich.


9

Cảm ơn tất cả các bạn đã đăng câu hỏi và câu trả lời của bạn: Nó đã giúp tôi giải quyết vấn đề này.

Trình điều khiển xem của tôi có chế độ xem bình thường.

Bên trong khung nhìn đó, có một số khung nhìn nhóm ít nhiều làm cho các khung nhìn con của chúng tương tác rõ ràng với các ràng buộc bố cục tự động.

Bên trong một trong những khung nhìn nhóm này, tôi có nút Thêm trình bày bộ điều khiển chế độ xem popover nơi người dùng nhập một số thông tin.

view
--groupingView
----addButton

Trong quá trình quay thiết bị, bộ điều khiển khung nhìn được cảnh báo thông qua lệnh gọi UIPopoverViewControllDelegate popoverContoder: willRepocationPopoverToRect: inView:

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

Phần thiết yếu xuất phát từ lời giải thích được đưa ra bởi hai câu trả lời đầu tiên ở trên là phần chỉnh sửa tôi cần chuyển đổi là giới hạn của nút thêm, không phải khung của nó.

Tôi đã không thử điều này với hệ thống phân cấp chế độ xem phức tạp hơn, nhưng tôi nghi ngờ rằng bằng cách sử dụng chế độ xem được cung cấp trong lệnh gọi phương thức (inView :) chúng ta sẽ khắc phục được các biến chứng của các kiểu xem lá nhiều tầng.


3
"Tôi cần chuyển đổi từ giới hạn của nút thêm, không phải khung." - về cơ bản đã cứu tôi khỏi rất nhiều mã khung xấu xí để làm cho nó hoạt động. Cảm ơn bạn đã dành thời gian để chỉ ra điều này
latenitecoder

3

Tôi đã sử dụng bài đăng này để áp dụng trong trường hợp của tôi. Hy vọng điều này sẽ giúp người đọc khác trong tương lai.

Một khung nhìn chỉ có thể nhìn thấy ngay lập tức quan điểm của trẻ em và phụ huynh. Nó không thể nhìn thấy cha mẹ lớn của nó hoặc quan điểm cháu của nó.

Vì vậy, trong trường hợp của tôi, tôi có một cái nhìn ông bà gọi là self.view, trong này self.viewtôi đã thêm subviews gọi self.child1OfView, self.child2OfView. Trong self.child1OfView, tôi đã thêm subviews gọi self.child1OfView1, self.child2OfView1.
Bây giờ nếu tôi thực sự di chuyển self.child1OfView1đến một khu vực bên ngoài ranh giới của self.child1OfViewđiểm bao phấn trên self.view, thì để tính toán vị trí mới cho self.child1OfView1bên trongself.view:

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

3

Bạn có thể xem mã dưới đây để bạn có thể hiểu rằng nó thực sự hoạt động như thế nào.

    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

Tôi đọc câu trả lời và hiểu cơ học nhưng tôi nghĩ ví dụ cuối cùng là không đúng. Theo tài liệu API, thuộc tính trung tâm của chế độ xem chứa điểm trung tâm đã biết của chế độ xem trong hệ thống tọa độ của giám sát.

Nếu đây là trường hợp, tôi nghĩ sẽ không có ý nghĩa gì khi cố gắng yêu cầu giám sát chuyển đổi trung tâm của một khung nhìn phụ TỪ hệ thống tọa độ của khung nhìn phụ vì giá trị không nằm trong hệ tọa độ của khung nhìn phụ. Điều có ý nghĩa là làm ngược lại, tức là chuyển đổi từ hệ tọa độ giám sát sang hệ thống phụ ...

Bạn có thể làm theo hai cách (cả hai sẽ mang lại cùng một giá trị):

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

hoặc là

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

Tôi có cách nào để hiểu làm thế nào điều này nên làm việc?


Ví dụ của tôi không chính xác, tôi đã cập nhật câu trả lời. Như bạn nói, thuộc tính trung tâm đã ở trong không gian tọa độ của giám sát.
jrturton

1

Một điểm quan trọng hơn về việc sử dụng các API này. Hãy chắc chắn rằng chuỗi khung nhìn chính hoàn thành giữa chỉnh lưu bạn đang chuyển đổi và chế độ xem đến / từ. Ví dụ: aView, bView và cView -

  • aView là một khung nhìn của bView
  • chúng tôi muốn chuyển đổi aView.frame sang cView

Nếu chúng tôi cố gắng thực hiện phương thức trước khi bView được thêm vào dưới dạng một khung nhìn phụ của cView, chúng tôi sẽ nhận được phản hồi bunk. Thật không may, không có sự bảo vệ được xây dựng trong các phương pháp cho trường hợp này. Điều này có vẻ rõ ràng, nhưng đó là điều cần lưu ý trong trường hợp chuyển đổi trải qua một chuỗi dài của cha mẹ.

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.