티스토리 뷰

반응형

[1]  용어정리

text selection 하면 나오는 등장하는 메뉴 팝업 UI를

 

https://developer.apple.com/design/human-interface-guidelines/ios/controls/edit-menus/

 

HIG 에서는 Edit Menus 라고 지칭하고 

개발 용어로는 UIMenuController 입니다. 

 

그리고 Edit Menu (UIMenuContoller) 안에 나오는 Cut, Copy.. 이런 것들을

Command, Action, MenuItem 이라는 용어로 혼용해서 부릅니다. 

 

 

[2] UIMenuController 커스터마이징 (1) - System

 

첫번째로,  UIMenuController 에 나오는 system commands 중 원하는 command 만 나오게 커스터마이징 하려면 

어떻게 해야할까요?

 

 

UIMenuController가 표시되기 전에  UIResponder 의 canPerformAction(_:withSender:) 이 호출됩니다. 

이 메소드를 오버라이딩 해서 print 해보면 

class CustomTextView: UITextView {
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        print(action)
        return super.canPerformAction(action, withSender: sender)
    }
}

 

UIMenuController가 나타나기 전에 

 

 

 

canPerformAction(_:withSender:)  이 여러번 호출되고 아래와 같은 action들이 프린트 됩니다. 

cut:
copy:
select:
selectAll:
paste:
delete:
_promptForReplace:
_transliterateChinese:
_insertDrawing:
...

 

이 중, true를 리턴해준 action들만 UIMenuController에 나오게 되는 것입니다. 

 

그래서 system commands 중, 원하는 command (또는 action) 만 나오게 커스터마이징 하고 싶다!! 

예를들어 cut 만 나오게 하고싶다!!!  라고 한다면, 이렇게 해주면 되겠죠? 

class CustomTextView: UITextView {
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(cut(_:)) {
            return true
        } else {
            return false
        }
    }
}

 

 

또는 텍스트뷰가 isEditable일 때, Cut은 기본 command 니까 이렇게 해줘도 무방합니다! 

class CustomTextView: UITextView {
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(cut(_:)) {
            return super.canPerformAction(action, withSender: sender)
        } else {
            return false
        }
     }
}

 

 

 

참고로 아까 출력된 actions들 중,  언더바가 안붙은 action은  

UIResponderStandardEditActions 에 정의되어있습니다.  (언더바 붙은 action은 어디에 정의되어있는 지 모르겠네요;;)

cut:
copy:
select:
selectAll:
paste:
delete:
_promptForReplace:
_transliterateChinese:
_insertDrawing:
...

 

UIResponderStandardEditActions 은 '앱이 editing을 지원하기 위해 채택할 수 있는 표준 메소드 모음' 으로, 

문서에서 아래와 같이 말하고 있습니다. 

 

"UIMenuController는 UIResponderStandardEditActions 의 메소드들을 호출해서 editing action 을 리포팅한다.

그 다음, UIMenuController는 UIKit에 적절한 메서드를 구현하는 객체를 검색하도록 요청하여 

해당 메서드를 구현하는 첫 번째 객체에 대해 메서드를 호출한다."

 

 

[3] UIMenuController 커스터마이징 (2) - Custom

두번째로,  system command 말고 custom command를 추가하고 싶다면 어떻게 해야할까요? 

 

menuItems  프로퍼티를 사용하면 됩니다.

 

 

menuItems의 디폴트값(no custom menu items)은 nil 입니다.

menuItems 를 통해 cutom menu item을 추가하면 system menu item 뒤에 나오게 됩니다. 

 

 

UIMenuController.shared 로 싱글톤 객체를 가져와서

세팅해보겠습니다. 

 

 

UIMenuController.shared.menuItems = [
    UIMenuItem(title: "안녕", action: #selector(sayHello))
]

@objc
private func sayHello() {
    print("hello")
}

 

맨 뒤에 Custom MenuItem이 잘 추가됩니다. 

 

 

 

그리고 만약 Custom MenuItem 만 나오게 하고 싶다고 하면,, 

아까 살펴본 것과 동일하게  canPerformAction(_:withSender:)  을 오버라이딩해서  custom behavior 를 추가하시면 됩니다. 

 

 

[4] UIMenuController의 update 메소드

 

update()  메소드를 통해 menu items 을 수정하고 이를 force update 를 시킬 수 도 있습니다.

이 메소드가 필요한 사례가 문서에 잘 나와있는데요, 

 

1) pasteboard에 데이터가 없어서 '붙여넣기' 를 비활성화시켜 Edit Menus 를 띄웠다.

2) 근데 pasteboard에 데이터가 생겼다!  

3) Edit Menus 를 강제업데이트하자! 

 

 

이런 상황에서 update를 콜하면 됩니다. 

 

간단한 테스트를 해봅시다.

몇초 후, 아이템을 '안녕' -> '방가'로 바꾸기!  with update

 

 

update를 하면 Edit Menus가 계속 띄워진 상태를 유지하며 안의 아이템들만 바뀌는 것이 아니라 

그냥 닫히는 것이네요!! 

닫혀서 사용자가 다시 클릭하면 업데이트된 Edit Menus 를 보여주는 방식입니다. 

 

 

참고로 만약 update를 안하면

시간이 지나도 계속 '안녕' 으로 남아있으니 위의 방식이 더 좋긴 합니다! 

 

 

 

 

[ 더 보면 좋을 것 ]

https://betterprogramming.pub/uimenucontroller-and-manipulating-the-responder-chain-c06fad73c64b

 

Using the UIMenuController and Manipulating the Responder Chain

A detailed guide to handling events through UIMenuController with Swift

betterprogramming.pub

 

[ 참고 ]

iOS 16 에  textView(_:editMenuForTextIn:suggestedActions:) 가 추가되었습니다 :-) 

 

 

 

 

 

반응형
댓글