Danny의 iOS 컨닝페이퍼
article thumbnail

[TIL #15] 2023 / 04 / 17

UIMunu는 iOS 13부터 추가된 기능입니다.

특정 오브젝트를 길게 누르거나, 특정 제스처에 나타나는 메뉴 커스터마이징 할 수 있습니다.

 

한번 UIMenu에 대해 간단히 알아봅시다.

 

 

UIMenu

일단 UIMenu 생성자를 확인하고 들어가겠습니다.

이미 Zedd님 이 설명을 잘해두셨네요.

 

이렇게 UIMenu의 생성자가 있고, 안쪽 파라미터에서 children 제외한 나머진 생략이 가능합니다.

 

여기서 children은 UIMenuElement로 돼있습니다.

 

children을 만들 때, 주의점으로는 UIMenuElement로 직접 객체를 만들어 사용하면 안 되고,

클래스 UIMenu, UIAction, UICommand로 객체 만들어 사용해줘야 합니다.

 

나머지 설명은 UIMenu를 만들어 본 후, 마지막에 사진과 함께 알아봅시다.


2가지 방법으로 UIMenu를 만들어 사용해 보려 합니다.

 

1. UIContextMenuInteraction을 통해 Menu 만들기 (iOS 13 이상)

2. UIButton의 menu 속성을 통해 Menu 만들기 (iOS 14 이상)

 

기본 화면 세팅은 이렇게 했습니다.

class Way1ViewController: UIViewController {
    
    let button: UIButton = {
        let button =  UIButton(frame: CGRect(x: 150, y: 200, width: 100, height: 40))
        button.backgroundColor = .gray
        button.layer.cornerRadius = 10
        button.setTitle("Button", for: .normal)
        return button
    }()
    
    let label: UILabel = {
        let label = UILabel(frame: CGRect(x: 150, y: 400, width: 100, height: 40))
        label.textAlignment = .center
        label.textColor = .black
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(button)
        view.addSubview(label)
        // 👉 Menu창을 바로 띄어주기 때문에, 굳이 버튼 동작을 만들어 줄 필요없어요!
        // button.addTarget(self, action: #selector(buttonHandler), for: .touchUpInside)
        
    }
    
    @objc func buttonHandler(_ sender: UIButton) {
    }
}

 

 

UIContextMenuInteraction을 통해 Menu 만들기

iOS 13 이상에서부터 사용이 가능합니다.

상호작용이 가능한 객채(UIButton 등)와 델리게이트를 통해, Menu 객체를 만들어 제공해 줍니다.

 

참고로 이 방법은 메뉴가 나타나면, 뒤에 위치한 화면은 뿌예집니다.

 

일단, 한번 만들어 봅시다.

 

먼저 UIAction으로 메뉴에 사용할 동작을 만들어 줍시다.

여기선 save, delete 동작을 만들어 줬어요.

// MARK: - Properties

var items: [UIAction] {
    
    let save = UIAction(
        title: "Save",
        image: UIImage(systemName: "plus"),
        handler: { [unowned self] _ in
            self.label.text = "Save"
        })

    let delete = UIAction(
        title: "Delete",
        image: UIImage(systemName: "trash"),
        handler: { [unowned self] _ in
            self.label.text = "Delete"
        })

    let Items = [save, delete]

    return Items
}

 

다음으로 UIContextMenuInteraction의 대리자를 설정해 줍니다.

 

여기서 UIContextMenuInteraction은 iOS 13 이상에서 추가된 상호작용 관련 기능으로,

사용자가 앱 내의 객체를 길게 누르면 메뉴를 표시하는 기능입니다.

// MARK: - Life Cycles

override func viewDidLoad() {
    super.viewDidLoad()

    let interaction = UIContextMenuInteraction(delegate: self)
    // 버튼의 상호작용을 추가해줍니다.
    button.addInteraction(interaction)
}

 

다음으로, UIContextMenuInteraction의 델리게이트 함수를 정의해 메뉴를 만들어 줍니다.

extension Way1ViewController: UIContextMenuInteractionDelegate {
    
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        
        return UIContextMenuConfiguration(actionProvider:  { [unowned self] suggestedActions in
            
            let menu = UIMenu(title: "메뉴1",
                              children: self.items)
     
            return menu
        })
    }
}

 

버튼을 길게 눌러주면 메뉴가 생성됩니다!

 

 

UIButton의 menu 속성을 통해 Menu 적용하기

iOS 14 이상에서 조금 더 편하게 사용할 수 있도록, 각종 버튼에 대해서 menu라는 속성이 생겼습니다.

(UIButton, UIBarButtonItem 등)

 

사용방법이 많이 간편해졌습니다. 얼마나 간편해졌나 사용해 봅시다.

 

위와 같이 먼저 UIAction들을 만들어 주겠습니다.

// MARK: - Properties

var items: [UIAction] {
    
    let save = UIAction(
        title: "Save",
        image: UIImage(systemName: "plus"),
        handler: { [unowned self] _ in
            self.label.text = "Save"
        })

    let delete = UIAction(
        title: "Delete",
        image: UIImage(systemName: "trash"),
        handler: { [unowned self] _ in
            self.label.text = "Delete"
        })

    let Items = [save, delete]

    return Items
}

 

버튼의 속성 menu로 접근하여 생성한 UIMenu를 넣어주면 끝!

만든 메서드를 viewDidLoad에서 실행해 주세요.

 

또한 iOS 14부터는 ⭐️showsMenuAsPrimaryAction⭐️으로 길게 버튼 클릭을 하지 않고도,

한 번의 탭만으로 메뉴를 나오게 설정해 줄 수 있습니다.

 

예를 들어, 버튼에서 addTarget을 사용하게 되면, 버튼 동작이 2개(메뉴 동작, addTarget 동작)가 존재하게 됩니다.

보통 addTarget은 탭 하면 동작하며, menu는 버튼을 길게 누르면 동작하게 됩니다.

showsMenuAsPrimaryAction 속성을 통해, 탭의 방식을 원하는 방향으로 만들어 사용할 수 있습니다.

func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      children: items)
 
    button.menu = menu
    button.showsMenuAsPrimaryAction = true
}

 

 

UIMenu 생성자 파라미터 설명

이제 위에서 설명 못 했던, UIMenu의 생성자에 대해서 알아봅시다.

 

 

children

children부터 살펴보면, children은 UIMenuElement 타입이고

사용 시엔 클래스 UIMenu, UIAction, UICommand로 객체를 만들어 사용한다고 했습니다.

 

신기하게도 UIMenu도 children에 넣어줄 수 있네요. 즉, UIMenu에 또 UIMenu를 넣을 수 있습니다.

100개도 가능하겠죠....

func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      children: items)
    
    let mainMenu = UIMenu(title: "메인",
                          children: [menu])
    
    button.menu = mainMenu
    button.showsMenuAsPrimaryAction = true
}

 

 

title

말 그대로 메뉴의 Title을 나타내 줍니다.

(생략해 줘도 무방합니다)

func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      children: items)
    
    let mainMenu = UIMenu(children: [menu])
    
    button.menu = mainMenu
    button.showsMenuAsPrimaryAction = true
}

 

mainMenu에 title을 추가해 준다면 이렇게 보이겠죠?

 

 

subtitle

Title 밑에 나타나는 Sub Title입니다.

 

참고로, subTitle은 처음 보이는 mainMenu에서는 사용이 불가능합니다.

즉, 최상단 메뉴에서는 subTitle이 표시가 안 된다.

(내부의 메뉴부터 표시됨, 그리고 UIAction에서도 subTitle 생성 가능)

func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      subtitle: "서브 타이틀",
                      children: items)
    
    let mainMenu = UIMenu(title: "메인",
                          children: [menu])
    
    button.menu = mainMenu
    button.showsMenuAsPrimaryAction = true
}

 

 

image

Title 옆에 나타나는 이미지입니다. 

subTitle과 마찬가지로, image는 처음 보이는 메뉴에는 사용이 불가능합니다.

(내부의 메뉴부터 표시됨, 그리고 UIAction에서도 image 생성 가능)

func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      subtitle: "서브 타이틀",
                      image: UIImage(systemName: "star"),
                      children: items)
    
    let mainMenu = UIMenu(title: "메인",
                          children: [menu])
    
    button.menu = mainMenu
    button.showsMenuAsPrimaryAction = true
}

 

 

 

identifier

메뉴의 고유 식별자라고 하는데, 잘 모르겠지만 타입 속성으로 뭔가 많이 정의돼 있네요?

아마도 상황에 맞게 사용할 상수를 넣어주면 될 것 같네요.

 

참고로 nil로 지정하면 고유 식별자로 생성이 된다고 하네요. (따로, 고유식별자를 만들어줘도 되고요)

자세한 내용은 공식문서 를 참고하세요

 

 

option

singleSelection(기본), displayInline(한 개의 라인), destructive(붉은색) 이렇게 존재합니다.

 

subTitle과 마찬가지로, option은 처음 보이는 메뉴에는 사용이 불가능합니다.

(내부의 메뉴부터 표시됨, 그리고 UIAction에는 attributes을 통해 생성 가능)

 

 

preferredElementSize

생성되는 메뉴의 사이즈를 정할 수 있습니다.

(small, medium, large)

func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      subtitle: "서브 타이틀",
                      image: UIImage(systemName: "star"),
                      identifier: nil,
                      options: .singleSelection,
                      preferredElementSize: .large,
                      children: items)
    
    let mainMenu = UIMenu(title: "메인",
                          children: [menu])
    
    button.menu = mainMenu
    button.showsMenuAsPrimaryAction = true
}

 

 

UIBarButtonItem에서 적용 방법

UIBarButtonItem에서는 UIMenu를 적용하는 방법은 위와 동일하게 menu 속성에 직접 넣어줘도 되고,

처음 UIBarButtonItem 생성 시, 생성자를 통해 UIMenu를 추가해 줘도 됩니다.

 

위와 마찬가지로 iOS +14부터 사용가능 합니다.

 

메뉴가 존재하는 생성자는 3가지가 있습니다.

// iOS +14 이상 사용가능
let barButton = UIBarButtonItem(systemItem: UIBarButtonItem.SystemItem,
                                primaryAction: UIAction?,
                                menu: UIMenu?)

// iOS +14 이상 사용가능
let barButton = UIBarButtonItem(title: String?,
                                image: UIImage?,
                                primaryAction: UIAction?,
                                menu: UIMenu?)

// iOS +16 이상 사용가능
let barButton = UIBarButtonItem(title: String?,
                                image: UIImage?,
                                target: AnyObject?,
                                action: Selector?,
                                menu: UIMenu?)

 

간단히 만들어 보면, 이와 같습니다.

var items: [UIAction] {
    
    let save = UIAction(
        title: "Save",
        image: UIImage(systemName: "plus"),
        handler: { [unowned self] _ in
            self.label.text = "Save"
        })
    
    let delete = UIAction(
        title: "Delete",
        image: UIImage(systemName: "trash"),
        handler: { [unowned self] _ in
            self.label.text = "Delete"
        })
    
    let Items = [save, delete]
    
    return Items
}
func setupMenu() {
    let menu = UIMenu(title: "메뉴",
                      children: items)
    
    // UIBarButtonItem의 생성자를 통해 메뉴를 생성하기
    let barButton = UIBarButtonItem(title: nil,
                                    image: UIImage(systemName: "list.dash"),
                                    primaryAction: nil,
                                    menu: menu)

    navigationItem.rightBarButtonItem = barButton
}

 

 

마무리

간단하게 UIMenu에 대해서 알아봤습니다. 전체 코드는 GitHub 를 참고하세요.

 

처음에 UIMenu가 있는 줄도 모르고, UIAlertAction며 PopOver도 사용했었는데,

완전 이건 뭐... 제가 생각한 모양이 아니더라고요.

 

한참 동안 찾아보다가,

오픈 채팅방의 고수님들에게 UIMenu란 키워드를 얻게 돼서 이렇게 사용해 봤습니다.

 

이런 게 있을 거라곤 상상도 못 했네요 ㅎㅎ

 

역시, Swift는 계속 파도 파도 새로운 기능이 나오네요. ㅠ.ㅠ

 

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!