Memahami convertRect: toView :, convertRect: FromView :, convertPoint: toView: dan convertPoint: fromView: metode

128

Saya mencoba memahami fungsi dari metode ini. Bisakah Anda memberi saya usecase sederhana untuk memahami semantik mereka?

Dari dokumentasi, misalnya, metode convertPoint: fromView: dijelaskan sebagai berikut:

Mengubah titik dari sistem koordinat pandangan yang diberikan ke penerima.

Apa arti sistem koordinat ? Bagaimana dengan penerima ?

Misalnya, apakah masuk akal menggunakan convertPoint: fromView: seperti berikut ini?

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

Menggunakan utilitas NSLog, saya telah memverifikasi bahwa nilai p bertepatan dengan pusat view1.

Terima kasih sebelumnya.

SUNTING: bagi mereka yang tertarik, saya telah membuat cuplikan kode sederhana untuk memahami metode ini.

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

Dalam kedua kasus, jendela mandiri adalah penerima. Tetapi ada perbedaan. Dalam kasus pertama, parameter convertPoint diekspresikan dalam koordinat view1. Outputnya adalah sebagai berikut:

convertPoint: fromView: {100, 100}

Yang kedua, sebagai gantinya, convertPoint dinyatakan dalam superview (self.window) koordinat. Outputnya adalah sebagai berikut:

convertPoint: toView: {0, 0}

Lorenzo B
sumber

Jawaban:

184

Setiap tampilan memiliki sistem koordinat sendiri - dengan asal pada 0,0 dan lebar dan tinggi. Ini dijelaskan dalam boundssegi empat tampilan. Ituframe pandang, namun, akan memiliki asal-usulnya pada titik dalam batas-batas persegi panjang dari SuperView nya.

Tampilan terluar dari hierarki tampilan Anda berasal dari 0,0 yang sesuai dengan kiri atas layar di iOS.

Jika Anda menambahkan subview pada 20,30 ke tampilan ini, maka titik pada 0,0 di subview sesuai dengan titik pada 20,30 dalam superview. Konversi ini adalah apa yang dilakukan metode-metode itu.

Contoh Anda di atas tidak ada gunanya (tidak ada permainan kata-kata yang dimaksudkan) karena ia mengubah suatu titik dari tampilan ke dirinya sendiri, sehingga tidak ada yang terjadi. Anda akan lebih umum mengetahui di mana titik pandang dalam kaitannya dengan superview - untuk menguji apakah pandangan bergerak dari layar, misalnya:

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

"Penerima" adalah istilah tujuan-c standar untuk objek yang menerima pesan (metode juga dikenal sebagai pesan) sehingga dalam contoh saya di sini penerima superview.

jrturton
sumber
3
Terima kasih atas balasan Anda, jrturton. Penjelasan yang sangat berguna. convertPointdan convertRectberbeda dalam jenis pengembalian. CGPointatau CGRect. Tapi bagaimana dengan fromdan to? Apakah ada aturan praktis yang bisa saya gunakan? Terima kasih.
Lorenzo B
dari kapan Anda ingin mengonversi dari, ke saat Anda ingin mengonversi ke?
jrturton
3
Bagaimana jika superview bukan tampilan induk langsung dari subview, apakah masih akan berfungsi?
Van Du Tran
3
@ VanDuTran ya, asalkan mereka berada di jendela yang sama (yang paling banyak dilihat di aplikasi iOS)
jrturton
Jawaban yang bagus Banyak membantu menjernihkan bagi saya. Ada bacaan tambahan?
JaeGeeTee
39

Saya selalu menemukan ini membingungkan jadi saya membuat taman bermain di mana Anda dapat menjelajahi secara visual apa convertfungsinya. Ini dilakukan dalam Swift 3 dan 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

Ingatlah untuk memperlihatkan Asisten Editor ( ) untuk melihat tampilan, tampilannya akan seperti ini:

masukkan deskripsi gambar di sini

Jangan ragu untuk berkontribusi lebih banyak contoh di sini atau di intisari ini .

phi
sumber
Terima kasih, ini sangat berguna dalam memahami konversi.
Anand
Ikhtisar hebat. Namun saya pikir itu self.viewberlebihan, penggunaan sederhana viewjika selftidak diperlukan.
Jakub Truhlář
Terima kasih @ JakubTruhlář, saya baru saja dihapusself
phi
Terima kasih banyak! Taman bermain Anda banyak membantu saya untuk mengetahui bagaimana konversi ini bekerja.
Anvar Azizov
30

Berikut ini penjelasan dalam Bahasa Inggris.

Saat Anda ingin mengonversi kotak dari subview ( aViewadalah subview dari [aView superview]) ke ruang koordinat tampilan lain ( 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];
sepatu kuda7
sumber
3
Ini tidak benar. Itu tidak menggerakkan pandangan, itu hanya memberi Anda koordinat pandangan dalam hal yang lain. Dalam kasus Anda itu akan memberi Anda koordinat aView dalam hal di mana mereka berada dalam diri.
Carl
Benar. Itu tidak memindahkan tampilan. Metode mengembalikan CGRect. Apa yang Anda pilih untuk dilakukan dengan CGRect adalah bisnis Anda. :-) Dalam kasus di atas, ini dapat digunakan untuk memindahkan satu tampilan ke hierarki yang lain sambil mempertahankan posisi visualnya di layar.
horseshoe7
14

Setiap tampilan di iOS memiliki sistem koordinat. Sistem koordinat sama seperti grafik, yang memiliki sumbu x (garis horizontal) dan sumbu y (garis vertikal). Titik di mana garis interesect disebut asal. Suatu titik diwakili oleh (x, y). Misalnya, (2, 1) berarti bahwa intinya adalah 2 piksel tersisa, dan 1 piksel turun.

Anda dapat membaca lebih lanjut tentang sistem koordinat di sini - http://en.wikipedia.org/wiki/Coordinate_system

Tapi yang perlu Anda ketahui adalah bahwa, di iOS, setiap tampilan memiliki sistem koordinatnya SENDIRI, di mana sudut kiri atas adalah asalnya. Sumbu X terus meningkat ke kanan, dan sumbu y terus meningkat ke bawah.

Untuk pertanyaan tentang titik konversi, ambil contoh ini.

Ada tampilan, yang disebut V1, yang lebar 100 piksel dan tinggi 100 piksel. Sekarang di dalam itu, ada pandangan lain, yang disebut V2, di (10, 10, 50, 50) yang berarti bahwa (10, 10) adalah titik dalam sistem koordinat V1 di mana sudut kiri atas V2 harus ditempatkan, dan ( 50, 50) adalah lebar dan tinggi V2. Sekarang, ambil titik di dalam sistem koordinat V2, katakan (20, 20). Sekarang, apa gunanya di dalam sistem koordinat V1? Itulah gunanya metode (tentu saja Anda dapat menghitung sendiri, tetapi mereka menghemat kerja ekstra). Sebagai catatan, titik dalam V1 adalah (30, 30).

Semoga ini membantu.

Saurav Sachidanand
sumber
9

Terima kasih semua telah mengirimkan pertanyaan dan jawaban Anda: Ini membantu saya menyelesaikan masalah ini.

Pengontrol tampilan saya memiliki tampilan normal.

Di dalam tampilan itu ada sejumlah pandangan pengelompokan yang tidak lebih dari memberikan pandangan anak mereka interaksi yang bersih dengan kendala tata letak otomatis.

Di dalam salah satu pandangan pengelompokan itu saya memiliki tombol Tambah yang menyajikan pengontrol tampilan popover tempat pengguna memasukkan beberapa informasi.

view
--groupingView
----addButton

Selama rotasi perangkat, pengendali tampilan diperingatkan melalui panggilan 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];
}

Bagian penting yang berasal dari penjelasan yang diberikan oleh dua jawaban pertama di atas adalah bahwa rect yang perlu saya konversi adalah batasnya tombol add, bukan bingkainya.

Saya belum mencoba ini dengan hierarki tampilan yang lebih kompleks, tetapi saya curiga bahwa dengan menggunakan tampilan yang disediakan dalam pemanggilan metode (inView :) kita dapat mengatasi berbagai kerumitan tampilan daun multi-tingkat jenis keburukan.

tobinjim
sumber
3
"Saya perlu mengkonversi dari batas tombol add, bukan bingkai." - pada dasarnya menyelamatkan saya dari banyak kode bingkai jelek untuk membuatnya bekerja. Terima kasih telah meluangkan waktu untuk menunjukkan ini
latenitecoder
3

Saya menggunakan pos ini untuk menerapkan dalam kasus saya. Semoga ini bisa membantu pembaca lain di masa depan.

Tampilan hanya dapat melihat tampilan langsung anak-anak dan orang tuanya. Itu tidak bisa melihat pandangan orang tua atau cucu-cucunya.

Jadi, dalam kasus saya, saya memiliki pandangan orang tua besar yang disebut self.view, dalam hal ini self.viewsaya telah menambahkan subviews disebut self.child1OfView, self.child2OfView. Dalam self.child1OfView, saya telah menambahkan subviews disebut self.child1OfView1, self.child2OfView1.
Sekarang jika saya secara fisik pindah self.child1OfView1ke area di luar batas self.child1OfViewuntuk anther spot on self.view, maka untuk menghitung posisi baru untuk self.child1OfView1dalamself.view:

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

Anda dapat melihat kode di bawah ini sehingga Anda dapat memahami bahwa cara kerjanya sebenarnya.

    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)

}
vikas prajapati
sumber
1

Saya membaca jawabannya dan memahami mekanika tetapi saya pikir contoh terakhirnya tidak benar. Menurut dokumen API, properti tengah tampilan berisi titik tengah tampilan yang dikenal dalam sistem koordinat superview.

Jika ini masalahnya, maka saya pikir tidak masuk akal untuk mencoba meminta superview untuk mengubah pusat subview DARI sistem koordinat subview karena nilainya tidak dalam sistem koordinat subview. Apa yang masuk akal adalah melakukan yang sebaliknya yaitu mengkonversi dari sistem koordinat superview ke subview ...

Anda dapat melakukannya dengan dua cara (keduanya harus menghasilkan nilai yang sama):

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

atau

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

Apakah saya mengerti bagaimana ini seharusnya bekerja?

Moonwalker
sumber
Contoh saya salah, saya telah memperbarui jawabannya. Seperti yang Anda katakan, properti pusat sudah di ruang koordinat superview.
jrturton
1

Satu lagi poin penting tentang penggunaan API ini. Pastikan rantai tampilan induk selesai antara kotak yang Anda konversi dan tampilan ke / dari. Misalnya - aView, bView, dan cView -

  • aView adalah subview dari bView
  • kami ingin mengonversi aView.frame ke cView

Jika kita mencoba menjalankan metode sebelum bView telah ditambahkan sebagai subview dari cView, kita akan mendapatkan respons bunk. Sayangnya tidak ada perlindungan yang dibangun ke dalam metode untuk kasus ini. Ini mungkin tampak jelas, tetapi itu adalah sesuatu yang harus diperhatikan dalam kasus-kasus di mana pertobatan berlangsung melalui rantai panjang orang tua.

D-rajin
sumber