Compreender os métodos convertRect: toView :, convertRect: FromView :, convertPoint: toView: e convertPoint: fromView:

128

Estou tentando entender as funcionalidades desses métodos. Você poderia me fornecer um simples caso de uso para entender a semântica deles?

Na documentação, por exemplo, o método convertPoint: fromView: é descrito da seguinte maneira:

Converte um ponto do sistema de coordenadas de uma determinada vista para o do receptor.

O que significa o sistema de coordenadas ? E o receptor ?

Por exemplo, faz sentido usar o convertPoint: fromView: como o seguinte?

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

Usando o utilitário NSLog, verifiquei que o valor p coincide com o centro da view1.

Agradeço antecipadamente.

EDIT: para os interessados, criei um snippet de código simples para entender esses métodos.

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

Nos dois casos, self.window é o receptor. Mas há uma diferença. No primeiro caso, o parâmetro convertPoint é expresso nas coordenadas da view1. A saída é a seguinte:

convertPoint: fromView: {100, 100}

No segundo, em vez disso, o convertPoint é expresso em coordenadas de superview (janela de auto). A saída é a seguinte:

convertPoint: toView: {0, 0}

Lorenzo B
fonte

Respostas:

184

Cada vista possui seu próprio sistema de coordenadas - com uma origem em 0,0 e uma largura e altura. Isso é descrito no boundsretângulo da vista. A framevisão, no entanto, terá sua origem no ponto dentro do retângulo de limites de sua super visão.

A visualização mais externa da hierarquia de visualizações tem origem em 0,0, que corresponde à parte superior esquerda da tela no iOS.

Se você adicionar uma subvisão em 20,30 a essa visualização, um ponto em 0,0 na subview corresponderá a um ponto em 20,30 na superview. Essa conversão é o que esses métodos estão fazendo.

Seu exemplo acima é inútil (sem trocadilhos), uma vez que converte um ponto de uma exibição em si mesmo, portanto nada acontecerá. Você descobriria com mais frequência onde estava um ponto de vista em relação à sua visão geral - para testar se uma visão estava saindo da tela, por exemplo:

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

O "receptor" é um termo objetivo-c padrão para o objeto que está recebendo a mensagem (os métodos também são conhecidos como mensagens), portanto, no meu exemplo, aqui está o receptor superview.

jrturton
fonte
3
Obrigado pela sua resposta, jrturton. Explicação muito útil. convertPointe convertRectdiferem no tipo de retorno. CGPointou CGRect. Mas frome quanto to? Existe uma regra prática que eu possa usar? Obrigado.
Lorenzo B
de quando você deseja converter de, para quando você deseja converter?
Jrturton
3
E se a superview não for a visão pai direta da subview, continuará funcionando?
Van Du Tran
3
@VanDuTran sim, enquanto eles estão na mesma janela (que a maioria dos pontos de vista em um aplicativo iOS são)
jrturton
Ótima resposta. Ajudou a esclarecer muito para mim. Alguma leitura adicional?
precisa saber é o seguinte
39

Eu sempre acho isso confuso, então criei um playground onde você pode explorar visualmente o que a convertfunção faz. Isso é feito no Swift 3 e no 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

Lembre-se de mostrar o Editor Assistente ( ) para ver as visualizações, deve ficar assim:

insira a descrição da imagem aqui

Sinta-se livre para contribuir com mais exemplos aqui ou nesta essência .

phi
fonte
Obrigado, isso foi realmente útil para entender as conversões.
Anand
Ótima visão geral. No entanto, acho que esses self.viewsão redundantes e de uso simples, viewse selfnão forem necessários.
Jakub Truhlář
Obrigado @ JakubTruhlář, acabei de removerself
phi
Muito obrigado! Seu playground me ajudou muito a descobrir como essas conversões funcionam.
Anvar Azizov
30

Aqui está uma explicação em inglês simples.

Quando você deseja converter o retângulo de uma subvisão ( aViewé uma subvisão de [aView superview]) para o espaço de coordenadas de outra visualização ( 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];
ferradura7
fonte
3
Isso não é verdade. Ele não move a visualização, apenas fornece as coordenadas da visualização em termos de outra. No seu caso, ele fornecerá as coordenadas do aView em termos de onde elas estariam.
18714 Carl Carl
Corrigir. Não move a vista. O método retorna um CGRect. O que você escolhe fazer com esse CGRect é o seu negócio. :-) No caso acima, ele poderia ser usado para mover uma visualização para a hierarquia de outra, mantendo sua posição visual na tela.
horseshoe7
14

Todas as visualizações no iOS têm um sistema de coordenadas. Um sistema de coordenadas é como um gráfico, que tem o eixo x (linha horizontal) e o eixo y (linha vertical). O ponto em que as linhas se interceptam é chamado de origem. Um ponto é representado por (x, y). Por exemplo, (2, 1) significa que o ponto está com 2 pixels restantes e 1 pixel abaixo.

Você pode ler mais sobre sistemas de coordenadas aqui - http://en.wikipedia.org/wiki/Coordinate_system

Mas o que você precisa saber é que, no iOS, toda visualização possui seu próprio sistema de coordenadas, onde o canto superior esquerdo é a origem. O eixo X continua aumentando para a direita e o eixo y continua aumentando.

Para a questão de pontos de conversão, pegue este exemplo.

Há uma exibição, chamada V1, com 100 pixels de largura e 100 pixels de altura. Agora, dentro disso, há outra visualização, chamada V2, em (10, 10, 50, 50), o que significa que (10, 10) é o ponto no sistema de coordenadas da V1 onde o canto superior esquerdo da V2 deve estar localizado e ( 50, 50) é a largura e a altura de V2. Agora, observe um ponto no sistema de coordenadas do V2, digamos (20, 20). Agora, qual seria esse ponto dentro do sistema de coordenadas da V1? É para isso que servem os métodos (é claro que você pode se calcular, mas economiza um trabalho extra). Para o registro, o ponto em V1 seria (30, 30).

Espero que isto ajude.

Saurav Sachidanand
fonte
9

Obrigado a todos por postar a pergunta e suas respostas: Isso me ajudou a resolver isso.

Meu controlador de exibição tem sua exibição normal.

Dentro dessa visualização, há várias visualizações de agrupamento que fazem pouco mais do que fornecer às visualizações filho uma interação limpa com restrições de layout automáticas.

Dentro de uma dessas visualizações de agrupamento, tenho um botão Adicionar que apresenta um controlador de exibição de popover em que o usuário digita algumas informações.

view
--groupingView
----addButton

Durante a rotação do dispositivo, o controlador de exibição é alertado por meio do popoverController de chamada UIPopoverViewControllerDelegate: willRepositionPopoverToRect: inView:

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

A parte essencial que vem da explicação dada pelas duas primeiras respostas acima foi que o correto que eu precisava converter era o limite do botão adicionar, não o quadro.

Não tentei isso com uma hierarquia de visualização mais complexa, mas suspeito que, usando a visualização fornecida na chamada de método (inView :), contornamos as complicações dos tipos de feiúra da visualização em folha em várias camadas.

tobinjim
fonte
3
"Eu precisava converter os limites do botão adicionar, não o quadro." - essencialmente me salvou de um monte de código de quadro feio para fazê-lo funcionar. Obrigado por tomar o tempo para apontar isso
latenitecoder
3

Eu usei este post para aplicar no meu caso. Espero que isso ajude outro leitor no futuro.

Uma visão pode ver apenas seus filhos imediatos e pais. Não consegue ver a visão de seus avós ou netos.

Então, no meu caso, eu tenho uma vista pai grande chamado self.view, neste self.vieweu adicionei subviews chamados self.child1OfView, self.child2OfView. Em self.child1OfView, eu adicionei subviews chamados self.child1OfView1, self.child2OfView1.
Agora, se eu me mover fisicamente self.child1OfView1para uma área fora do limite de outro self.child1OfViewlocal self.view, calcule a nova posição para o self.child1OfView1interior doself.view:

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

Você pode ver o código abaixo para entender como ele realmente funciona.

    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
fonte
1

Li a resposta e entendi a mecânica, mas acho que o exemplo final não está correto. De acordo com o documento da API, a propriedade central de uma vista contém o ponto central conhecido da vista no sistema de coordenadas da superview.

Se for esse o caso, acho que não faria sentido tentar solicitar à superview a conversão do centro de uma subview do sistema de coordenadas da subview, porque o valor não está no sistema de coordenadas da subview. O que faria sentido é fazer o oposto, ou seja, converter do sistema de coordenadas da superview para o de uma subview ...

Você pode fazer isso de duas maneiras (ambas devem gerar o mesmo valor):

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

ou

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

Estou muito longe de entender como isso deve funcionar?

Moonwalker
fonte
Meu exemplo estava incorreto, eu atualizei a resposta. Como você diz, a propriedade center já está no espaço de coordenadas da superview.
Jrturton 26/10/12
1

Mais um ponto importante sobre o uso dessas APIs. Certifique-se de que a cadeia de visualizações pai esteja completa entre o retângulo que você está convertendo e a visualização de / para. Por exemplo - aView, bView e cView -

  • aView é uma subview do bView
  • queremos converter aView.frame em cView

Se tentarmos executar o método antes que o bView seja adicionado como uma sub-visualização do cView, receberemos uma resposta de bunk. Infelizmente, não há proteção incorporada aos métodos para este caso. Isso pode parecer óbvio, mas é algo a ter em atenção nos casos em que a conversão passa por uma longa cadeia de pais.

D-avid
fonte