티스토리 뷰

스위프트/UIKit

[UIKit] - UIMenu 적용 예제

강철곰탱이 2024. 2. 10. 22:06

 

UIMenu를 예제를 통해 사용해보자.

📢 StoryBoard를 사용하지 않고 코드로 구현!!

 

 

 

 

1️⃣ UI 만들기

 

UI는 다음과 같이 설정하였다.

 

자주 사용하는 CustonVerticalView와 CustomNextBtn은 따로 View로 뻬서 관리하고 VC로 가져오기만 하였다.

이렇게 자주 사용하는View나 UI 요소를 따로 관리하면 추후 코드 유지 보수 하는데 도움된다.

 

CreateFolderVC는 상속받은 CustomNavigationBar와 가져온 View와 ui 요소 그리고 UIMenu를 사용하기 위한 ui 요소들로 구성되어져 있다.

 

상속받거나 가져온 요소들을 제외한 folderButton, parentFolderLabel, selectedFolderLabel은 위의 view를 구현하기 위해서다.

 

  • parentFolderLabel: "상위 폴더" label
  • stackView: [selectedFolderLabel, folderButton]
  • selectedFolderLabel: UIMenu에서 선택한 데이터
  • folderButton: UIMenu 보여줄 버튼
import UIKit
import SnapKit

class CreateFolderVC: CustomNavigationBar{
    
    let folderButton = UIButton()
    let parentFolderLabel = UILabel()
    let selectedFolderLabel = UILabel()
    
    let folderNameInputView = CustomVerticalView(labelText: "폴더 이름", placeholder: "이름")
    let createButton = CustomNextBtn(title: "폴더 만들기")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        initView()
    }
    
    func initView(){
        
        view.backgroundColor = .white
        
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 8
        stackView.distribution = .equalSpacing
        stackView.layer.borderWidth = 1
        stackView.layer.cornerRadius = 8
        stackView.layer.borderColor = UIColor(named: "Gray3")?.cgColor
        stackView.addArrangedSubview(selectedFolderLabel)
        stackView.addArrangedSubview(folderButton)
        
        selectedFolderLabel.text = "선택"
        
        folderButton.setImage(UIImage(named: "category"), for: .normal)
        
        parentFolderLabel.text = "상위 폴더"
        parentFolderLabel.font = .boldSystemFont(ofSize: 18)
        
        view.addSubview(parentFolderLabel)
        view.addSubview(stackView)
        view.addSubview(folderNameInputView)
        view.addSubview(createButton)
        
        parentFolderLabel.snp.makeConstraints { make in
            make.height.equalTo(25)
            make.leading.equalToSuperview().inset(16)
            make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(32)
        }
        
        selectedFolderLabel.snp.makeConstraints{make in
            make.leading.equalToSuperview().inset(16)
        }
        
        stackView.snp.makeConstraints { make in
            make.height.equalTo(56)
            make.top.equalTo(parentFolderLabel.snp.bottom).offset(8)
            make.leading.trailing.equalToSuperview().inset(16)
        }
        
        folderNameInputView.snp.makeConstraints{make in
            make.top.equalTo(stackView.snp.bottom).offset(24)
            make.leading.trailing.equalToSuperview().inset(16)
            make.height.equalTo(88)
        }
        
        createButton.snp.makeConstraints{make in
            make.bottom.equalTo(view.snp.bottom).offset(-50)
            make.leading.trailing.equalToSuperview().inset(16)
        }
    }
}

 

 

  • CustomNavigationBar

또한, Navigation Bar도 VC로 만들어놓고 상속받아 사용하도록 하여 유지보수를 하는데 도움이 되었다.

이런 ui의 Navigation Bar는 많이 사용해서 상속받아서 사용하면 쉽고 빠르게 코드를 구현할 수 있다.

 

class CustomNavigationBar: UIViewController {
    
    private var titleLabel: UILabel!
    private var closeButton: UIButton!
    private var currentTitle = ""

    init(title: String) {
        super.init(nibName: nil, bundle: nil)
        setupNavigationBar(title: title)
        currentTitle = title
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    private func setupNavigationBar(title: String) {
        // X button
        closeButton = UIButton(type: .system)
        closeButton.setImage(UIImage(named: "close_icon"), for: .normal)
        closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: closeButton)
        
        // TitleView
        titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.textColor = .black
        titleLabel.font = UIFont.boldSystemFont(ofSize: 16.0)
        titleLabel.sizeToFit()
        
        let titleView = UIView(frame: CGRect(x: 0, y: 0, width: titleLabel.frame.width, height: titleLabel.frame.height))
        titleView.addSubview(titleLabel)
        
        self.navigationItem.titleView = titleView
    }
    @objc func closeButtonTapped() {
        
    }
}

 

  • CustonVerticalView

import UIKit
import SnapKit

class CustomVerticalView: UIView {
    var titleLabel: UILabel = {
        let label = UILabel()
        label.font = .boldSystemFont(ofSize: 18)
        return label
    }()

    var textInputField =  UITextField()

    init(labelText: String, placeholder: String ) {
        super.init(frame: .zero)
        titleLabel.text = labelText
        textInputField.placeholder = placeholder
        setupViews()
    }

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

    private func setupViews() {
        addSubview(titleLabel)
        addSubview(textInputField)
        
        textInputField.font = .systemFont(ofSize: 14)
        textInputField.layer.borderWidth = 1
        textInputField.layer.cornerRadius = 8
        textInputField.layer.borderColor = UIColor(named: "Gray3")?.cgColor
        textInputField.leftView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 16.0, height: 0.0))
        textInputField.leftViewMode = .always

        titleLabel.snp.makeConstraints { make in
            make.height.equalTo(25)
            make.top.equalTo(self.snp.top).offset(10)
        }
        
        textInputField.snp.makeConstraints { make in
            make.height.equalTo(56)
            make.top.equalTo(titleLabel.snp.bottom).offset(8)
            make.leading.trailing.equalToSuperview()
        }
    }
}

 

  • CustomNextBtn

import Foundation
import UIKit

class CustomNextBtn: UIButton {

    init(title: String) {
       super.init(frame: .zero)
       
        setTitle(title, for: .normal)
        titleLabel?.font = UIFont.systemFont(ofSize: 14)
        setTitleColor(.white, for: .normal)
        backgroundColor = UIColor(named: "Gray3")
        layer.cornerRadius = 5
       
       snp.makeConstraints { make in
           make.height.equalTo(55)
       }
   }
       
   required init?(coder: NSCoder) {
       fatalError("init(coder:) has not been implemented")
   }

}

 

 

만약 이런 코드들을 한 VC에 다 때려넣으면 코드가 진짜,,, 엄청 난리 나겠지 ㅎㅎㅎ

그래서 이렇게 최대한 view와 ui 요소들을 VC로부터 분리하여 사용하고자 한다.

 

2️⃣ UIMenu 적용

 

 

folderButton 클릭시 작동할 액션 함수를 지정해준다.

 folderButton.addTarget(self, action: #selector(showMenu), for: .touchUpInside)

 

  • showMenu()
@objc private func showMenu() {
        
        let dataArray = ["data1", "data2"] // data 설정
        
        var menuItems = [UIAction]()
        
        for data in dataArray {
            let action = UIAction(title: data) { _ in
                self.selectedFolderLabel.text = data
            }
            menuItems.append(action)
        }
        
        let menu = UIMenu(title: "title", children: menuItems)
        
        self.folderButton.menu = menu
        self.folderButton.showsMenuAsPrimaryAction = true
    }

 

 

  • UIAction

UIMenu를 설정하는데 UIAction이 왜 필요한가 의문이었다.

찾아보니 iOS 13 이상부터는 UIAction을 사용하여 메뉴 및 버튼의 동작을 정의할 수 있다고 한다.

클로저를 통해 동작이 수행될 때 실행할 코드를 지정할 수 있고, 이를 통해 메뉴 아이템의 동작을 구성할 수 있다고,,,

 

암튼 사용해야한다고 한다.

 

var menuItems = [UIAction]()

for data in dataArray {
    let action = UIAction(title: data) { _ in
        self.selectedFolderLabel.text = data
    }
    menuItems.append(action)
}

 

배열의 data들 다 가져와서 UIAction으로 만들고 menuItems에 추가해준다. 그럼 해당 데이터가 선택되었을 때 selectedFolderLabel의 text가 바뀌게 된다.

 

let menu = UIMenu(title: "title", children: menuItems)

self.folderButton.menu = menu
self.folderButton.showsMenuAsPrimaryAction = true

 

 

공식문서에서 title, image, identifier 등등 사용할 수 있다는데 난 title과 children만 사용하였다.

children이 이제 UIMenuElement를 넣는 거여서 menuItems를 넣어준다.

 

그러면 생성된 menu가 folderButton에 할당되고, showsMenuAsPrimaryAction을 true로 해준다.

여기까지해야 버튼을 누르면 메뉴가 나오면서 위에서 설정한 dataArray의 값들이 나온다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
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 31
글 보관함