Things take time

[SWIFT] Autolayout 사용하기 - 가로/세로 변경 및 하위뷰 변경 본문

iOS (기능)

[SWIFT] Autolayout 사용하기 - 가로/세로 변경 및 하위뷰 변경

겸손할 겸 2020. 9. 25. 11:04

[목적]

 

간만의 오토레이아웃.. 제약조건 하기

 

목적은 다음과 같다.

두개의 뷰(검정, 노랑)를 가진 하나의 컨테이너 역할을 하는 뷰(초록), 총 세개의 뷰가 있다.

컨테이너 뷰는 가로/세로에 따라 너비 높이가 달라지고, 안의 두 개의 뷰는 아래와 같이 변경되어야한다.

 

또한 유동적인 넓이를 가진 뷰(파랑)가 있으며, 이 뷰는 컨테이너뷰의 영역을 제외한 영역을 모두 차지한다.

 

 

위와 같은 조건을 사용하기 위해 제약조건을 걸어보겠다.

 

제약조건을 활용할때, 가장 먼저 생각할것

 

1. 스토리보드상에서 해결할 수 있는가

=> 스토리보드에서 간단하게 해결된다면, 그냥 사용하면 된다. 가장 좋은 방법이지만 위와 같은 것에서는 해결할 수 없다. 그래도 최대한 스토리 보드상에서 해결할 수 있어야 하므로, 아래와 같이 기본을 작성했다.

 

처음 열릴때 컨테이너뷰는 가로로 있는 위치일 것이고, 안에 들어갈 뷰는 코드로 직접 작성하여 넣을 것이다.

컨테이너 뷰는 넓이가 달라지지만, 고정된 값 2개 중 하나의 값을 가질것이므로, 파란뷰의 넓이는 따로 지정하지 않으며, 컨테이너 뷰와 부모 뷰와의 간격정도만 지정했다.

 

 

[코드]

 

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var btn: UIButton!
    
    @IBOutlet weak var vContain: UIView!
    @IBOutlet weak var vContainWidth: NSLayoutConstraint!
    @IBOutlet weak var vContainHeight: NSLayoutConstraint!
    
    var i = 0
    var v1: UIView?
    var v2: UIView?
    var v1WidthConstraint: NSLayoutConstraint?
    var v1HeightConstraint: NSLayoutConstraint?
    var v1LeadingConstraint: NSLayoutConstraint?
    var v1TrainlingConstraint: NSLayoutConstraint?
    var v1TopConstraint: NSLayoutConstraint?
    var v1BottomthConstraint: NSLayoutConstraint?
    var v2WidthConstraint: NSLayoutConstraint?
    var v2HeightConstraint: NSLayoutConstraint?
    var v2LeadingConstraint: NSLayoutConstraint?
    var v2TrainlingConstraint: NSLayoutConstraint?
    var v2TopConstraint: NSLayoutConstraint?
    var v2BottomthConstraint: NSLayoutConstraint?
    
    @IBOutlet weak var v1BottomConstraint: NSLayoutConstraint?
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        v1 = UIView()
        v1!.backgroundColor = UIColor.black
        self.vContain.addSubview(v1!)
        v1!.translatesAutoresizingMaskIntoConstraints = false
        v2 = UIView()
        v2!.backgroundColor = UIColor.yellow
        self.vContain.addSubview(v2!)
        v2!.translatesAutoresizingMaskIntoConstraints = false
        
        
        v1WidthConstraint = NSLayoutConstraint(item: v1!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 80)
        v1HeightConstraint = NSLayoutConstraint(item: v1!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 80)
        v1LeadingConstraint = NSLayoutConstraint(item: v1!, attribute: .leading, relatedBy: .equal, toItem: vContain, attribute: .leading, multiplier: 1, constant: 10)
        v1TrainlingConstraint = NSLayoutConstraint(item: v1!, attribute: .trailing, relatedBy: .equal, toItem: vContain, attribute: .trailing, multiplier: 1, constant: -10)
        v1TopConstraint = NSLayoutConstraint(item: v1!, attribute: .top, relatedBy: .equal, toItem: vContain, attribute: .top, multiplier: 1, constant: 10)
        v1BottomthConstraint = NSLayoutConstraint(item: v1!, attribute: .bottom, relatedBy: .equal, toItem: vContain, attribute: .bottom, multiplier: 1, constant: -10)
        
        v2WidthConstraint = NSLayoutConstraint(item: v2!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 80)
        v2HeightConstraint = NSLayoutConstraint(item: v2!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 80)
        v2LeadingConstraint = NSLayoutConstraint(item: v2!, attribute: .leading, relatedBy: .equal, toItem: vContain, attribute: .leading, multiplier: 1, constant: 10)
        v2TrainlingConstraint = NSLayoutConstraint(item: v2!, attribute: .trailing, relatedBy: .equal, toItem: vContain, attribute: .trailing, multiplier: 1, constant: -10)
        v2TopConstraint = NSLayoutConstraint(item: v2!, attribute: .top, relatedBy: .equal, toItem: vContain, attribute: .top, multiplier: 1, constant: 10)
        v2BottomthConstraint = NSLayoutConstraint(item: v2!, attribute: .bottom, relatedBy: .equal, toItem: vContain, attribute: .bottom, multiplier: 1, constant: -10)
        

        vContainHeight.constant = 200
        vContainWidth.constant = 100
        self.v1WidthConstraint?.isActive = true
        self.v1HeightConstraint?.isActive = true
        self.v1LeadingConstraint?.isActive = true
        self.v1TrainlingConstraint?.isActive = true
        self.v1TopConstraint?.isActive = true
        self.v1BottomthConstraint?.isActive = false
        
        
        self.v2WidthConstraint?.isActive = true
        self.v2HeightConstraint?.isActive = true
        self.v2LeadingConstraint?.isActive = true
        self.v2TrainlingConstraint?.isActive = true
        self.v2TopConstraint?.isActive = false
        self.v2BottomthConstraint?.isActive = true
    }

    @IBAction func btnClicked(_ sender: UIButton) {
        if i % 2 == 0{
            
            vContainHeight.constant = 100
            vContainWidth.constant = 200
            self.v1WidthConstraint?.isActive = true
            self.v1HeightConstraint?.isActive = true
            self.v1LeadingConstraint?.isActive = true
            self.v1TrainlingConstraint?.isActive = false
            self.v1TopConstraint?.isActive = true
            self.v1BottomthConstraint?.isActive = true
            
            
            self.v2WidthConstraint?.isActive = true
            self.v2HeightConstraint?.isActive = true
            self.v2LeadingConstraint?.isActive = false
            self.v2TrainlingConstraint?.isActive = true
            self.v2TopConstraint?.isActive = true
            self.v2BottomthConstraint?.isActive = true
        }else{
            
            
            vContainHeight.constant = 200
            vContainWidth.constant = 100
            self.v1WidthConstraint?.isActive = true
            self.v1HeightConstraint?.isActive = true
            self.v1LeadingConstraint?.isActive = true
            self.v1TrainlingConstraint?.isActive = true
            self.v1TopConstraint?.isActive = true
            self.v1BottomthConstraint?.isActive = false
            
            
            self.v2WidthConstraint?.isActive = true
            self.v2HeightConstraint?.isActive = true
            self.v2LeadingConstraint?.isActive = true
            self.v2TrainlingConstraint?.isActive = true
            self.v2TopConstraint?.isActive = false
            self.v2BottomthConstraint?.isActive = true
        }
        i += 1
    }
}

코드가 길어보이지만, 원리는 간단하다.

안에 들어갈 두 개의 뷰의 제약조건을 다 코드화했으며, 단순히 isActive를 통해 활성화 비활성화만 하면 된다.

컨테이너 뷰가 가로일 때의 기준

1. 검은색 뷰는 넓이, 높이가 정해져있으므로(80) 상단, 하단, 그리고 왼쪽(leading)조건만 있으면 된다. 그리고 오른쪽(trailing)조건은 사용하지 않아야한다. 노란색 뷰가 위치할 것이기 때문이다.

2. 노란색 뷰는 검은색 뷰와 다른점은 왼쪽 조건을 비활성화하고, 오른쪽 조건을 활성화하면 된다.

컨테이너 뷰가 세로일 때 기준

1. 검은색 뷰는 상단, 좌 우, 높이, 넓이는 정해지지만 하단은 정해지면 안된다. 노란색 뷰가 위치할 것이기 때문이다.

2. 노란색 뷰는 상단이 정해지면 안되며, 좌 우, 하단, 높이, 넓이가 정해지면 된다.

 

 

이처럼 컨테이너 뷰의 모습에 따라, 안에 들어갈 뷰의 제약조건이 달라져야한다면 이는 스토리보드상으로만 해결할 수 없다. 이럴땐 코드상 방법밖에 없으므로 유의하자.

 

그리고 위와 같은 결과를 낼 때, 위의 방법 말고도 다양하게 있다.

 

1. 좌, 우, 상, 하의 제약조건 대신 centerX, centerY의 제약조건을 걸어 이를 비활성화, 활성화 하는 방법도 있다.

2. 검은색 뷰와 노란색 뷰 간의 간격을 지정해주는 제약조건을 걸어 이를 비활성화, 활성화 하는 방법도 있다.

 

똑같은 기능을 구현하는데 개발자마다 각기 다른 스타일이 있는 것처럼, 제약조건도 여러가지 방법이 있다.

 

중요한 것은

1. 스토리보드상에서 해결되는 것이 가장 좋다.

2. 가변적인 제약조건은 코드밖에 없다.

 

 

주의점

위와 같이 코드상으로하기보다, 스토리보드상에서 제약조건을 걸고 이를 IBOutlet으로 연결하면?

이 방법을 먼저 생각했는데, 결론은 추천하지 않는다. 일단, 넓이 높이와 같은 제약조건은 IBOutlet으로 거는 것이 맞다. 그 제약조건을 비활성화 하지 않으니까, 근데 사용하지 않을 수 있는 제약조건(위의 예에서 좌우, 상단의 경우)은 일단 스토리보드상에서 만들 수가 없다.

 

스토리보드에서 컨테이너가 가로일 때를 먼저 그렸으면, 각 검은/노란 뷰의 세로 간격에 대한  제약조건을 연결할 수 없다. 반대로 컨테이너가 세로일때를 먼저 그렸으면, 검은/노란 뷰의 가로 간격에 대한 제약조건을 연결할 수 없는 것처럼.