ConvertRect: toView :, convertRect: FromView :, convertPoint: toView: ve convertPoint: fromView: yöntemlerini anlayın


128

Bu yöntemlerin işlevlerini anlamaya çalışıyorum. Anlambilimlerini anlamak için bana basit bir kullanım durumu sağlayabilir misiniz?

Belgelerden, örneğin, convertPoint: fromView: yöntemi aşağıdaki gibi açıklanmıştır:

Belirli bir görünümün koordinat sisteminden bir noktayı alıcınınkine dönüştürür.

Koordinat sistemi ne anlama geliyor? Alıcı ne olacak ?

Örneğin, convertPoint: fromView: aşağıdaki gibi kullanmak mantıklı mı?

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

NSLog yardımcı programını kullanarak, p değerinin view1'in merkezine denk geldiğini doğruladım.

Şimdiden teşekkür ederim.

DÜZENLEME: ilgilenenler için, bu yöntemleri anlamak için basit bir kod parçası oluşturdum.

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

Her iki durumda da self.window alıcıdır. Ama bir fark var. İlk durumda, convertPoint parametresi view1 koordinatlarında ifade edilir. Çıktı şu şekildedir:

convertPoint: fromView: {100, 100}

İkincisinde, bunun yerine, convertPoint denetleme (self.window) koordinatlarında ifade edilir. Çıktı şu şekildedir:

convertPoint: toView: {0, 0}

Yanıtlar:


184

Her görünümün kendi koordinat sistemi vardır - başlangıç ​​noktası 0,0'da ve genişlik ve yükseklikte. Bu, boundsgörünümün dikdörtgeninde açıklanmaktadır . frameSınırlar onun Superview arasında dikdörtgen içinde görüş Ancak noktasında kendi orjine sahip olacaktır.

Görünüm hiyerarşinizin en dış görünümünün kaynağı, iOS'ta ekranın sol üst kısmına karşılık gelen 0,0 değerindedir.

Bu görünüme 20,30'da bir alt görünüm eklerseniz, alt görünümdeki 0,0 noktasındaki bir nokta, süpervizör görünümünde 20,30'daki bir noktaya karşılık gelir. Bu dönüşüm, bu yöntemlerin yaptığı şeydir.

Yukarıdaki örneğiniz anlamsızdır (kelime oyunu değildir) çünkü bir noktayı bir bakış açısından kendisine dönüştürür, böylece hiçbir şey olmaz. Bir görünümün gözetimle ilişkili olarak bir bakış açısının nerede olduğunu daha yaygın olarak öğrenirsiniz - örneğin bir görünümün ekrandan hareket edip etmediğini test etmek için:

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

"Alıcı", mesajı alan nesne için standart bir hedef-c terimidir (yöntemler aynı zamanda mesajlar olarak da bilinir), bu yüzden benim örneğimde alıcıdır superview.


3
Cevabınız için teşekkür ederim jrturton. Çok faydalı açıklama. convertPointve convertRectdönüş türünde farklılık gösterir. CGPointveya CGRect. Peki ya fromve to? Kullanabileceğim bir pratik kural var mı? Teşekkür ederim.
Lorenzo B

ne zaman dönüştürmek istediğinizden, ne zaman dönüştürmek istediğinize kadar?
jrturton

3
Ya gözetim alt görünümün doğrudan üst görünümü değilse, yine de işe yarar mı?
Van Du Tran

3
@VanDuTran evet, aynı pencerede oldukları sürece (bir iOS uygulamasındaki çoğu görüntülemenin olduğu)
jrturton

Mükemmel cevap. Bana çok yardımcı oldu. Ek okuma var mı?
JaeGeeTee

39

Bunu her zaman kafa karıştırıcı buluyorum, bu yüzden convertişlevin ne yaptığını görsel olarak keşfedebileceğiniz bir oyun alanı yaptım . Bu, Swift 3 ve Xcode 8.1b'de yapılır:

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

Görünümleri görmek için Yardımcı Düzenleyiciyi ( ) göstermeyi unutmayın , aşağıdaki gibi görünmelidir:

görüntü açıklamasını buraya girin

Buraya veya bu ana fikre daha fazla örnek eklemekten çekinmeyin .


Teşekkürler, bu, dönüşümleri anlamada gerçekten yararlı oldu.
Anand

Harika bir genel bakış. Ancak self.viewbunların gereksiz olduğunu düşünüyorum , ihtiyaç viewyoksa basit kullanım self.
Jakub Truhlář

Teşekkürler @ JakubTruhlář, az önce kaldırdımself
phi

Çok teşekkürler! Oyun alanınız, bu dönüşümlerin nasıl işlediğini anlamamda bana çok yardımcı oldu.
Anvar Azizov

30

İşte sade İngilizce bir açıklama.

Bir alt görünümün dikdörtgenini ( aViewalt [aView superview]görünümdür self) başka bir görünümün koordinat alanına ( ) dönüştürmek istediğinizde.

// 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
Bu doğru değil. Görünümü hareket ettirmez, sadece size görüşün koordinatlarını diğerine göre verir. Sizin durumunuzda, size kendi içinde nerede olacakları açısından aView koordinatlarını verecektir.
Carl

Doğru. Görünümü hareket ettirmez. Yöntem bir CGRect döndürür. Bu CGRect ile yapmayı seçtiğiniz şey sizin işinizdir. :-) Yukarıdaki durumda, ekrandaki görsel konumunu korurken bir görünümü diğerinin hiyerarşisine taşımak için kullanılabilir.
at

14

İOS'taki her görünümün bir koordinat sistemi vardır. Bir koordinat sistemi, x ekseni (yatay çizgi) ve y ekseni (dikey çizgi) olan bir grafik gibidir. Çizgilerin kesiştiği noktaya başlangıç ​​noktası denir. Bir nokta (x, y) ile temsil edilir. Örneğin, (2, 1), noktanın 2 piksel sol ve 1 piksel aşağıda olduğu anlamına gelir.

Koordinat sistemleri hakkında daha fazla bilgiyi buradan okuyabilirsiniz - http://en.wikipedia.org/wiki/Coordinate_system

Ancak bilmeniz gereken şey, iOS'ta her görünümün, sol üst köşenin başlangıç ​​noktası olduğu KENDİ koordinat sistemine sahip olmasıdır. X ekseni sağa doğru artmaya devam ediyor ve y ekseni aşağı doğru artmaya devam ediyor.

Dönüştürme noktaları sorusu için bu örneği ele alalım.

V1 adında 100 piksel genişliğinde ve 100 piksel yüksekliğinde bir görünüm var. Şimdi bunun içinde, (10, 10, 50, 50) 'de V2 adında başka bir görünüm var, bu da (10, 10)' un, V1'in koordinat sisteminde V2'nin sol üst köşesinin bulunması gereken nokta olduğu anlamına gelir ve ( 50, 50), V2'nin genişliği ve yüksekliğidir. Şimdi, V2'nin koordinat sisteminin İÇİNDE bir nokta alın, diyelim ki (20, 20). Şimdi, V1'in koordinat sisteminde bu nokta ne olabilir? Yöntemler bunun için (elbette kendi kendinize hesaplayabilirsiniz, ancak size fazladan işten tasarruf sağlarlar). Kayıt için, V1'deki nokta (30, 30) olacaktır.

Bu yardımcı olur umarım.


9

Soruyu ve cevaplarınızı gönderdiğiniz için hepinize teşekkür ederim: Bunu halletmeme yardımcı oldu.

Görüntü denetleyicimin normal görünümü var.

Bu görünümün içinde, çocuk görünümlerine otomatik düzen kısıtlamalarıyla temiz bir etkileşim vermekten biraz daha fazlasını yapan bir dizi gruplama görünümü vardır.

Bu gruplama görünümlerinden birinin içinde, kullanıcının bazı bilgileri girdiği bir açılır pencere görünümü denetleyicisini sunan bir Ekle düğmesine sahibim.

view
--groupingView
----addButton

Cihaz dönüşü sırasında görünüm denetleyicisi, UIPopoverViewControllerDelegate çağrısı popoverController: willRepositionPopoverToRect: inView: aracılığıyla uyarılır:

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

Yukarıdaki ilk iki cevabın verdiği açıklamadan gelen en önemli kısım, dönüştürmem gereken düzeltmenin çerçevesi değil, ekle düğmesinin sınırları olduğuydu .

Bunu daha karmaşık bir görünüm hiyerarşisiyle denemedim, ancak yöntem çağrısında (inView :) sağlanan görünümü kullanarak çok katmanlı yaprak görünümü tür çirkinliklerinin komplikasyonlarını aştığımızdan şüpheleniyorum.


3
"Dönüştürmem gereken çerçeve değil, ekle düğmesinin sınırlarıydı." - aslında çalışmasını sağlamak için beni birçok çirkin çerçeve kodundan kurtardı. Bunu belirtmek için zaman ayırdığınız için teşekkürler
latenitecoder

3

Bu yazıyı durumuma başvurmak için kullandım. Umarım bu gelecekte başka bir okuyucuya yardımcı olur.

Bir görünüm yalnızca yakın alt öğelerini ve üst görünümlerini görebilir. Büyük ebeveynlerini veya torunlarının görüşlerini göremez.

Yani, benim durumumda, ben olarak adlandırılan bir büyükbaba görünüme sahip self.viewbu, self.viewben denilen subviews eklemiş self.child1OfView, self.child2OfView. In self.child1OfViewaradım subviews eklemiş self.child1OfView1, self.child2OfView1.
Şimdi fiziksel self.child1OfView1olarak sınırın dışındaki başka bir self.child1OfViewnoktaya hareket edersem , içerideki self.viewyeni konumu hesaplamak self.child1OfView1içinself.view:

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

3

Aşağıdaki kodu görebilirsiniz, böylece gerçekte nasıl çalıştığını anlayabilirsiniz.

    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

Cevabı okudum ve mekaniği anlıyorum ama son örneğin doğru olmadığını düşünüyorum. API belgesine göre, bir görünümün center özelliği, süpervizörün koordinat sistemindeki görünümün bilinen merkez noktasını içerir.

Eğer durum buysa, süpervizörden bir alt görüntünün merkezini alt görünüm koordinat sisteminden dönüştürmesini istemenin mantıklı olmayacağını düşünüyorum çünkü değer alt görünüm koordinat sisteminde değildir. Mantıklı olan, tersini yapmaktır, yani süpervizör koordinat sisteminden bir alt görünümün koordinat sistemine dönüştürmek ...

Bunu iki şekilde yapabilirsiniz (her ikisi de aynı değeri vermelidir):

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

veya

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

Bunun nasıl çalışması gerektiğini anlamada yetersiz miyim?


Örneğim yanlıştı, cevabı güncelledim. Dediğiniz gibi, merkez özelliği zaten süpervizörün koordinat uzayında.
jrturton

1

Bu API'leri kullanmayla ilgili bir önemli nokta daha. Üst görünüm zincirinin dönüştürmekte olduğunuz yön ile görünümden / görünümden arasında tamamlandığından emin olun. Örneğin - aView, bView ve cView -

  • aView, bView'ın bir alt görünümüdür
  • aView.frame'i cView'a dönüştürmek istiyoruz

Yöntemi, bView cView alt görünümü olarak eklenmeden önce yürütmeye çalışırsak, bir ranza yanıtı alacağız. Ne yazık ki bu durum için yöntemlerde yerleşik bir koruma yoktur. Bu aşikar görünebilir, ancak dönüşümün uzun bir ebeveyn zincirinden geçtiği durumlarda dikkat edilmesi gereken bir şeydir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.