THIS IS ELLIE

확장 가능한 커스텀 버튼 만들기 본문

개발/iOS

확장 가능한 커스텀 버튼 만들기

Ellie Kim 2021. 3. 31. 20:44

이전에 커스텀한 버튼을 만든 적이 있었는데요 저는 제약조건을 수정해서 확장시켰는데
깃을 보다가 Ale Patron이 분의 방식이 좋아 보여서 기록해놓으려 합니다.

기존 코드에서 맘대로 조금 수정을 했습니다.
버튼이 정중앙에 배치되어있는데 오른쪽 하단으로 옮겼고 색상을 변경했습니다.
버튼의 텍스트와 함수명을 변경했습니다.
버튼 사이즈 상수는 ButtonPanelView에서만 사용되고 있어 클래스 내부로 들고 왔습니다.
(내부로 들고 오면서 접근 제어자 fileprivate에서 private로 변경했습니다)
프로토콜은 클래스만 사용할 수 있도록 class로 수정했습니다.
버튼에 쉐도우 레이어는 제거했습니다.

그럼 다시 시작. 

class ButtonPanelView: UIView {
    private let buttonSize: CGFloat = 50
    weak var delegate: ButtonPanelDelegate?
    
    lazy var menuButton: UIButton = {
        let button = UIButton(frame: .zero)
        button.setTitle("⭕️", for: .normal)
        button.backgroundColor = .clear
        button.layer.cornerRadius = buttonSize / 2
        button.addTarget(
            self, action: #selector(tappedPanelButton(_:)), for: .touchUpInside)
        return button
    }()
    
    lazy var firstButton: UIButton = {
        let button = UIButton(frame: .zero)
        button.setTitle("1️⃣", for: .normal)
        button.layer.cornerRadius = buttonSize / 2
        button.isHidden = true
        button.addTarget(
            self, action: #selector(tappedExpadedButton(_:)), for: .touchUpInside)
        return button
    }()
    
    lazy var secondButton: UIButton = {
        let button = UIButton(frame: .zero)
        button.setTitle("2️⃣", for: .normal)
        button.layer.cornerRadius = buttonSize / 2
        button.isHidden = true
        button.addTarget(
            self, action: #selector(tappedExpadedButton(_:)), for: .touchUpInside)
        return button
    }()
    
    lazy var expandedStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.isHidden = true
        stackView.addArrangedSubview(firstButton)
        stackView.addArrangedSubview(secondButton)
        return stackView
    }()
    
    lazy var containerStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.addArrangedSubview(expandedStackView)
        stackView.addArrangedSubview(menuButton)
        return stackView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.lightGray
        layer.cornerRadius = buttonSize / 2
        addSubview(containerStackView)
        setConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setConstraints() {
        menuButton.translatesAutoresizingMaskIntoConstraints = false
        menuButton.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
        menuButton.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
        
        firstButton.translatesAutoresizingMaskIntoConstraints = false
        firstButton.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
        firstButton.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
        
        secondButton.translatesAutoresizingMaskIntoConstraints = false
        secondButton.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
        secondButton.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
        
        containerStackView.translatesAutoresizingMaskIntoConstraints = false
        containerStackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        containerStackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        
        translatesAutoresizingMaskIntoConstraints = false
        self.widthAnchor.constraint(equalTo: containerStackView.widthAnchor).isActive = true
        self.heightAnchor.constraint(equalTo: containerStackView.heightAnchor).isActive = true
    }
}

xib를 사용해서 초기화하지 않기 때문에 required init?(coder: NSCoder)는 비워져 있습니다. 
버튼 사이즈는 원하는 크기로 두면 됩니다.
아래에서 버튼 사이즈를 사용하기 때문에 따로 상수로 사용하는 게 좋습니다.
delegate는 버튼을 눌렀을 때 이벤트를 전송하기 위해 존재합니다.

그리고 각각의 버튼을 선언했습니다.
필요한 버튼은 총 3개로 펼치기 전에 보이는 버튼과 펼쳤을 때 추가로 2개의 버튼이 필요합니다.
그리고 펼쳤을 때 2개의 버튼을 감싸는 expandedStackView를 생성하고 숨김으로 처리했습니다.
그리고 총 3개의 버튼을 감싸는 containerStackView를 생성합니다.
이 containerStackView에는 1개의 버튼과 expendedStackView가 들어갑니다. 
setConstraints함수 내에서는 각각의 제약조건을 잡습니다.

이벤트 전송을 위해 델리게이트 프로토콜을 만들고 사용합니다.

protocol ButtonPanelDelegate: class {
    func didTapButtonWithText(_ text: String)
}

ButtonPanelDelegate프로토콜은 ViewController에서 채택하고 이벤트를 받습니다.

뷰 컨트롤러에서 버튼을 생성해주고 뷰에 넣어주고 제약을 잡아줍니다.

class ViewController: UIViewController {

    private let buttonPanelView = ButtonPanelView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupUI()
        setupConstraint()
    }

    func setupUI() {
        buttonPanelView.delegate = self
        view.addSubview(buttonPanelView)
    }
    
    func setupConstraint() {
        buttonPanelView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16).isActive = true
        buttonPanelView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -48).isActive = true
    }
}

extension ViewController: ButtonPanelDelegate {
    func didTapButtonWithText(_ text: String) {
        print(text)
    }
}

extension을 통해 어떤 버튼이 클릭되었는지 문자열을 전송함으로 파악할 수 있습니다.

resource:
github.com/apatronl/Expandable-Button-Demo

반응형