Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
UIKit/AutoLayout

19 - StaciView를 활용한 버튼 묶음_Floating buttons

by Toughie 2023. 4. 12.

우측 하단의 상자를 누르면 여러 메뉴가 뿅 튀어나오는 예시.

 

먼저 큰 그림.

저 파란상자 객체(버튼)를 뷰컨트롤러의 VieDidLoad안에서 

생성해주고, 뷰에 올리고(잊지말자 addSubView),

수동으로 레이아웃을 잡아준다. (trailing, bottom만 잡아주면 된다.) 잊지말자 오토리사이징마스크 false!

 

그리고 뷰컨트롤러에서 생성해줄 버튼의 파일을 따로 만들자.

UIView를 상속하는 클래스이다.

 

크게는 버튼이 있고, 안에 여러 메뉴가 담겨있는 스택이 있다.

버튼, 스택 변수는 init(frame: CGRact)에서 함수를 통해 초기화 시켜 준다.

required init?(coder: NSCoder)도 잊지 말자.

 

초기화 함수들은 클래스 내부에서 바로 구현하기 보다는 (클래스가 너무 비대해 지니까)

확장을 통해 구현한다.

 

# 뷰컨트롤러 코드

//
//  ViewController.swift
//  floatingButtons_prac
//
//  Created by Toughie on 2023/04/11.
//

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let menuButton = MenuButton()
        view.addSubview(menuButton)
        
        menuButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            menuButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            menuButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20)])
    }
}

 

메뉴버튼 코드

//
//  MenuButton.swift
//  floatingButtons_prac
//
//  Created by Toughie on 2023/04/07.
//

import UIKit

class MenuButton: UIView {
    
    private var menuButton: UIButton!
    private var menuStack: UIStackView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addMenuButton()
        addMenuStack()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


extension MenuButton {
    private func addMenuButton() {
        //버튼 초기 설정
        let button = UIButton(type: .system)
        let image = UIImage(systemName: "tray", withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle))
        
        button.setImage(image, for: .normal)
        button.tintColor = .systemGray
        addSubview(button)
        
        //오토레이아웃 코드
        button.translatesAutoresizingMaskIntoConstraints = false
        //topAnchor
        let buttonTopConstraint = button.topAnchor.constraint(equalTo: topAnchor)
        //우선도를 낮춰놔야 안의 메뉴들이 위로 올라올 수 있음_ 우선도가 높으면 해당 제약 때문에 메뉴가 못올라옴
        buttonTopConstraint.priority = .defaultLow
        buttonTopConstraint.isActive = true
        
        NSLayoutConstraint.activate([
            button.leadingAnchor.constraint(equalTo: leadingAnchor),
            button.bottomAnchor.constraint(equalTo: bottomAnchor),
            button.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
        //버튼 탭했을 때 동작 _ 안에 메뉴들이 위로 올라와야함
        button.addTarget(self, action: #selector(tapMenuButton), for: .touchUpInside)
        
        //버튼 초기화
        menuButton = button
    }
    
    @objc private func tapMenuButton() {
        //뿅 튀어나오는 애니메이션
        UIView.animate(withDuration: 0.5, //0.5초동안
                       delay: 0, //터치하는 즉시
                       usingSpringWithDamping: 0.5,//0에 가까울수록 통~튀는 느낌, 1은 스무스하게
                       initialSpringVelocity: 1, //초기 스프링 속도, 근데 값을 바꿔도 큰 차이를 모르겠음{ ㅋㅋ}
                       
                       // Reference to property 'menuStack' in closure requires explicit use of 'self' to make capture semantics explicit Reference 'self.' explicitly
                       // Capture 'self' explicitly to enable implicit 'self' in this closure
                       //클로저 캡처리스트
                       animations: { [self] in
            //스택뷰에 담겨있는 버튼들
            menuStack.arrangedSubviews.forEach { button in
                button.isHidden.toggle()
            }
            // 레이아웃 즉시 업데이트를 위한 코드
            menuStack.layoutIfNeeded()
        }, completion: nil
        )
    }
    
    private func addMenuStack() {
        let stack = UIStackView()
        stack.axis = .vertical
        addSubview(stack)
        
        let buttonImageNames = ["swift","lasso.and.sparkles","paperplane.fill","figure.archery","cloud.fill"]
        let buttonColors = [UIColor.orange, UIColor.blue, UIColor.cyan, UIColor.systemPink, UIColor.green]
        
        for i in 0..<buttonImageNames.count {
            //해당 심볼들을 가지고 버튼을 만든다.
            let button = UIButton(type: .system)
            let image = UIImage(systemName: buttonImageNames[i], withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle))
            button.setImage(image, for: .normal)
            
            //스택뷰에 버튼들을 담는다.
            stack.addArrangedSubview(button)
            
            //메뉴버튼을 눌렀을 때 해당 버튼들이 나타나야 하기 때문에 숨겨둔다.
            button.isHidden = true
            button.tintColor = buttonColors[i]
        }
        stack.translatesAutoresizingMaskIntoConstraints = false
        //스택뷰의 탑 제약.
        let stackTopConstraint = stack.topAnchor.constraint(equalTo: topAnchor)
        // 우선도를 조정하지 않아도 동작은 한다. (다만 메뉴버튼의 탑제약보다는 우선순위가 높아야함)
        stackTopConstraint.priority = .defaultHigh
        stackTopConstraint.isActive = true
        
        NSLayoutConstraint.activate([
            stack.centerXAnchor.constraint(equalTo: centerXAnchor),
            //메뉴버튼의 탑에서 위로 간격 띄워서
            stack.bottomAnchor.constraint(equalTo: menuButton.topAnchor, constant: -8)
        ])
        //초기화
        menuStack = stack
    }
}

 

쉬운 이해를 위해 주석이 달려 있다 :)

완성~

 

[학습 소스]

공식문서, 야곰 오토레이아웃 정복하기 강의

https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html

https://yagom.net/courses/autolayout/https://yagom.net/courses/autolayout/