Search code examples
iosswiftuiviewcoordinatescgrect

Subviews frame not updating after view.center command


What I have are these different views as shown in picture: the main view is the container, inside that there is the boxSentence, inside the boxSentence there is the subView, and inside subView the different text labels with the gray box boxWord. Then there is this other view boxAnswer that is a draggable element that i created that can only be dropped inside the boxWord (I pass the boxWord to my custom class of the boxAnswer to check his position (frame)).

Main scenario

In this scenario everything works fine, the boxAnswer can only be placed inside the boxWord

Working scenario

Then for adjusting the graphics of the whole view I want to put my subView with all his actual subviews in the center of boxSentence. To do that I used subView.center = boxSentence.center and I obtained what I wanted.

subView in the center

Now, the problem comes when I try to drag and drop boxAnswer inside the new position of boxWord, it doesn't work, but if I try to drag and drop it where boxWord was before moving it to the center it does work.

Problem here

It seems like that when you do subView.center = boxSentence.center it only affects the frame of subView, but it doesn't affect the frames of its subviews. Any suggestions on how to resolve the problem?

CODE:

// creo box dove inserire la frase
let boxSentence = UIView(frame: CGRect(x: 0, y: 0, width: container.frame.width, height: container.frame.height/2))
container.addSubview(boxSentence)
boxSentence.backgroundColor = hexStringToUIColor(hex: "#49eb34")

let subView = UIView(frame: CGRect(x: 0, y: 0, width: container.frame.width, height: 70))


let label1 = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
label1.text = esercizio.ctsex![0].first_sentence
label1.backgroundColor = UIColor.yellow
label1.sizeToFit()

let boxWord = UIView(frame: CGRect(x: label1.frame.maxX, y: 0, width: 100, height: 60))
boxWord.backgroundColor = UIColor.gray

let label2 = UILabel(frame: CGRect(x: boxWord.frame.maxX, y: 0, width: 100, height: 100))
label2.text = esercizio.ctsex![0].second_sentence
label2.backgroundColor = UIColor.yellow
label2.sizeToFit()

subView.addSubview(label1)
subView.addSubview(boxWord)
subView.addSubview(label2)
subView.backgroundColor = UIColor.purple
boxSentence.addSubview(sottoView)
subView.frame = CGRect(x: 0, y: 0, width: label2.frame.maxX-label1.frame.minX, height: 80)
subView.center = boxSentence.center


let coordrisposta = CGRect(x: boxFrase.frame.minX, y: boxFrase.frame.maxY, width: 80.0, height: 40.0)
let boxAnswer = DragObject(frame: coordrisposta, vistap: boxWord)
boxAnswer.backgroundColor = UIColor.red

container.addSubview(rispostaBox)

CLASS DragObject OF boxAnser:

import UIKit

// CLASSE CHE CREA UNA VISTA CHE PUO ESSERE SPOSTATA SOLO ALL'INTERNO DI UNA VISTA PASSATA DA PARAMETRO

class DragObject: UIView {

    var vista: UIView
    var initcoord: CGRect

    init(frame: CGRect, vistap: UIView){
        self.vista = vistap
        self.initcoord = frame
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touch began")
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
       var touch : UITouch! = touches.first as UITouch?
       self.center = touch.location(in: self.superview)        
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        var coorv = vista.frame
        print(coorv)

        let coord1 = (touches.first?.location(in: self.superview))!
        let destinazione1 = CGPoint(x: coorv.minX, y: coorv.minY)
        let destinazione2 = CGPoint(x: coorv.maxX, y: coorv.maxY)

        if coord1.x > destinazione1.x && coord1.y > destinazione1.y && coord1.x < destinazione2.x && coord1.y < destinazione2.y {
            print("OK")
            self.frame = vista.frame
        } else {
            print("fuori")
            self.frame = initcoord
        }
    }
}

Solution

  • Frames of UIView is it's coordinate relative to it's parent bounds(internal coordinate system of UIView that is coordinate system for subview of that view). For example we have UIViewController with 2 views:

    let redView = UIView()
    redView.backgroundColor = .red
    redView.frame = CGRect(x: 20, y: 20, width: 100, height: 100)
    self.view.addSubview(redView)
    
    let greenView = UIView()
    greenView.backgroundColor = .green
    greenView.frame = CGRect(x: 25, y:25, width:50, height: 50)
    redView.addSubview(greenView)
    
    print(redView.frame)
    /*
    (20.0, 20.0, 100.0, 100.0)
    redView is placed self.view.bounds.origin.x + 20 and self.view.bounds.origin.y + 20
    */
    print(greenView.frame)
    /*
    (25.0, 25.0, 50.0, 50.0)
    green is placed redView.bounds.origin.x + 25 and redView.bounds.origin.y + 25
    */
    print(redView.convert(greenView.frame, to: self.view))
    /*
    (45.0, 45.0, 50.0, 50.0)
    converted frame of greenView from redViews bounds to of self.view bounds.
    */
    
    

    So the problem inside DragObject, you pass frame and view coordinates that reflect their place relative to subView view origin. Meanwhile DragObject is subview of container view and when you receive touch coordinates you get coordinates relative to container view.
    You could fix it by converting vista.frame to DragObject superview coordinate system. Here is fullCode of override func touchesEnded(_ touches: Set, with event: UIEvent?) in DragObject

    var coorv = vista.superview!.convert(vista.frame, to: self.superview!)
    print(coorv)
    
    let coord1 = (touches.first?.location(in: self.superview))!
    let destinazione1 = CGPoint(x: coorv.minX, y: coorv.minY)
    let destinazione2 = CGPoint(x: coorv.maxX, y: coorv.maxY)
    
    if coord1.x > destinazione1.x && coord1.y > destinazione1.y && coord1.x < destinazione2.x && coord1.y < destinazione2.y {
        print("OK")
        self.frame = coorv
    } else {
        print("fuori")
        self.frame = initcoord
    }