목차

티스토리 뷰

iOS/Swift

[iOS] 커스텀 뷰 만들기

Assum 2023. 5. 25. 13:30
728x90
반응형

안녕하세요 🐾

개발을 진행하다보면 요구에 의해 다양한 커스텀 뷰를 만들어야 합니다.

코드를 이용해 Custom View를 만드는 방식을 공유합니다 :-)

이와 더불어 커스텀 뷰 생산성에 도움을 줄 [Code Snippet(코드 스니펫), 코드 즐겨찾기]도 참고하시는 것을 추천합니다.


 

1. 커스텀 뷰 템플릿 생성

템플릿을 만들어두면 커스텀 뷰 생성 후, 템플릿 코드를 붙여 넣은 뒤 일부 코드만 수정 구현하는 것이 편리하여 개인적으로 사용하는 방법입니다. 템플릿은 아래와 같이 작성하였습니다.

import UIKit

class UIViewTemplate: UIView {
    // MARK: - Properties
    
    // MARK: - UI Properties
    
    // MARK: - Initialize
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Functions
    private func setupUI() {
        self.backgroundColor = .white
    }
}

 

템플릿 코드를 보시면 먼저 주석의 코드영역을 표시하는 마킹 기능을 활용하였습니다.

클래스 탭 상단의 마지막 경로 부분을 눌러보면 아래와 같이 마킹한 구역이 나누어 표시되고 선택하면 선택된 영역으로 하이퍼링크가 가능해 집니다.

 

코드에 보이는 required init 생성자가 보이시나요?

UIView를 상속받아 지정 생성자를 사용할 경우 required init?을 필수로 작성해야 합니다.

왜 그럴까요? 바로 NSCoding 때문입니다.

UIView를 살펴보면 아래와 같이 NSCoding이란 프로토콜을 채택하고 있는걸 확인 할 수 있습니다.

UI 코드로만 생성되는 것이 아니라 스토리보드나 xib로 만들어질 때에는 NSCoding에 속한 init(coder: NSCoder) 을 통해 객체가 생성됩니다.

지정 생성자 선언 후  init(coder: NSCoder) 생성자에 대한 코드를 자동 완성하면,  fatalError로  코드가 채워지는 것을 확인 할 수 있습니다.

 

이는  '지정 생성자를 통해 뷰를 만들었으니 스토리보드나 xib로 만드는 것은 안된다' 는 일종의 경고로 보시면 됩니다 :)

 

 

2. 커스텀 뷰 생성

하나의 이미지뷰와 라벨을 가진 커스텀 뷰를 생성하도록 하겠습니다.

레이아웃 배치시 오토레이아웃 코드에 대한 생산성을 위해 Snapkit을 같이 사용했습니다.

import UIKit
import SnapKit

class AssumView: UIView {
    // MARK: - Properties
    
    // MARK: - UI Properties
    let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16, weight: .semibold)
        label.textAlignment = .center
        label.textColor = .darkGray
        label.text = "Assum"
        return label
    }()
    let iconImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.image = UIImage(named: "icon")
        return imageView
    }()
    
    // MARK: - Initialize
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Functions
    private func setupUI() {
        self.backgroundColor = .lightGray.withAlphaComponent(0.3)
        
        // iconImageView
        addSubview(iconImageView)
        iconImageView.snp.makeConstraints {
            $0.top.equalToSuperview()
            $0.centerX.equalToSuperview()
            $0.width.height.equalTo(48)
        }
        // nameLabel
        addSubview(nameLabel)
        nameLabel.snp.makeConstraints {
            $0.top.equalTo(iconImageView.snp.bottom).offset(12)
            $0.left.right.bottom.equalToSuperview()
            $0.width.equalTo(60)
            $0.height.equalTo(20)
        }
    }
}

 

3. 커스텀 뷰 생성(기존 컴포넌트 상속)

UILabel, UIButton처럼 기존 컴포넌트의 기능은 일부 이용하면서 UI만을 살짝 커스텀하고 싶을때에는

아래와 같이 해당 컴포넌트만을 상속받은 뒤 필요한 속성을 오버라이드하여 재정의하는 것도 좋은 방법입니다.

class AssumButton: UIButton {

    // MARK: - Enumeration
    enum buttonStyle: Int {
        case primary = 0
        case danger
    }
    
    // MARK: - Properties
    var style: buttonStyle = .primary {
        didSet {
            self.setupUI()
        }
    }
    
    override var isEnabled: Bool {
        didSet {
            self.setupUI()
        }
    }
    
    override var isHighlighted: Bool {
        didSet {
            self.setupUI()
        }
    }
    
    // MARK: - Initialize
    required init(style: buttonStyle) {
        super.init(frame: CGRect.zero)
        self.style = style
        self.setupUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Functions
    func setupUI() {
        // Common setup
        layer.masksToBounds = true
        layer.cornerRadius = 6
        titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
        
        // Indivisual setup
        switch self.style {
        case .primary:
            setTitleColor(.white, for: .normal)
            setTitleColor(.white, for: .disabled)
            setTitleColor(.white, for: .highlighted)
        case .danger:
            setTitleColor(.red, for: .normal)
            setTitleColor(.red, for: .disabled)
            setTitleColor(.red, for: .highlighted)
        }
        self.layoutIfNeeded()
    }
}

 

4. 커스텀 뷰 사용

뷰컨트롤러에서 위에 구현했던 커스텀 뷰들을 추가하고 배치합니다.

class ViewController: UIViewController {
    // MARK: - Properties
    
    // MARK: - UI Properties
    let assumView = AssumView()
    let assumButton = AssumButton(style: .primary)
    
    // MARK: - Initialize
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        setupUI()
    }
    
    // MARK: - Functions
    func setupUI() {
        view.backgroundColor = .white
        
        // assumView
        view.addSubview(assumView)
        assumView.snp.makeConstraints {
            $0.center.equalToSuperview()
            $0.width.height.greaterThanOrEqualTo(0)
        }
        
        // assumButton
        view.addSubview(assumButton)
        assumButton.snp.makeConstraints {
            $0.bottom.equalTo(view.safeAreaLayoutGuide)
            $0.left.right.equalToSuperview().inset(16)
            $0.height.equalTo(40)
        }
    }
}

 

실행하여 결과를 확인합니다.

 

반응형
댓글
300x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함