Swift(iOS)でイケてるトースト・バナー・ポップアップを導入する (SwiftEntryKit)

# SwiftEntryKit

トーストやポップアップ、プッシュ通知のようなビューを簡単に導入できるすごいライブラリ [SwiftEntryKit](https://github.com/huri000/SwiftEntryKit) をこの記事で紹介する。いくつか同様のライブラリ([Toast-Swift](https://github.com/scalessec/Toast-Swift)など)を試したが、SwiftEntryKitが圧倒的にイケてるUIUXを作れると感じた。イメージは以下の通り。

| Toasts | Notes | Floats | Popups |
| --- | --- | --- | --- |
| ![toasts_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/41fbd3bf-5e71-1eb4-982c-d52896800712.gif) | ![notes_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/3a7621f2-b255-3c33-8395-8945eb1c8eff.gif) | ![floats_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/5933b10d-6768-b437-6105-77fddd7e88f5.gif) | ![popups_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/3544d23c-64cc-52eb-9e5d-55e61c385f58.gif) |

| Alerts | Forms | Rating | More... |
| --- | --- | --- | --- |
| ![alerts_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/d320eae0-c281-d86c-4252-91a2126c7d8a.gif) | ![forms_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/1f439353-109d-7844-e430-a777988ab3c3.gif) | ![rating_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/ebcc5803-75bb-fa1e-b508-65dc7dc47c23.gif) | ![custom_example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/ceb9e4c2-2ec8-faad-258e-be3c1c28b3c9.gif) |


## インストール

https://github.com/huri000/SwiftEntryKit#installation

ここに丁寧に書いてあるのでここでは言及しない。CocoaPodsやCarthageで入れるだけ。

## 使い方のイメージ

```swift
import SwiftEntryKit

///

func test() {
    var attributes = EKAttributes.bottomFloat // bottomFloat以外に様々なタイプがある
    // プロパティを設定していく
    // attributes.xxx = xxx
    // たとえば以下の通り
    attributes.entryBackground = .gradient(gradient: .init(colors: [EKColor(UIColor.red), EKColor(UIColor.orange)], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
    attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.3), scale: .init(from: 1, to: 0.7, duration: 0.7)))
    attributes.shadow = .active(with: .init(color: .black, opacity: 0.5, radius: 10, offset: .zero))
    attributes.statusBar = .light
    attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
    attributes.positionConstraints.maxSize = .init(width: .constant(value: UIScreen.main.bounds.width), height: .intrinsic)

    // EKNotificationMessageViewなど、EKxxxViewを作ってattributesとともにSwiftEntryKitに渡す
    // UIFont() は任意の形に変更する必要がある
    let title = EKProperty.LabelContent(text: "Sample Notification", style: .init(font: UIFont(), color: .standardContent))
    let description = EKProperty.LabelContent(text: "Body message", style: .init(font: UIFont(), color: .standardContent))
    let image = EKProperty.ImageContent(image: UIImage(named: "some image"), size: CGSize(width: 35, height: 35))
    let simpleMessage = EKSimpleMessage(image: image, title: title, description: description)
    let notificationMessage = EKNotificationMessage(simpleMessage: simpleMessage)
    let contentView = EKNotificationMessageView(with: notificationMessage)

    // あとはSwiftEntryKit.displayを呼ぶだけ
    SwiftEntryKit.display(entry: contentView, using: attributes)
}
```

このライブラリでは、ビューのトップ、中央、ボトムの3つの位置（`top` / `center` / `bottom`）を選択でき、ビューのタイプは以下のような感じで10個以上定義されている。位置とビューのタイプを組み合わせて使うことになる。

- EKAlertMessageView
- EKFormMessageView
- EKNotificationMessageView
- EKNoteMessageView
- EKPopUpMessageView
- EKRatingMessageView


## iOSのプッシュ通知のようなバナーを表示する

```swift
struct NotificationBanner {
    static func show(title: String, body: String, image: UIImage) {
        var attributes = EKAttributes.topFloat
        attributes.entryBackground = .gradient(gradient: .init(colors: [EKColor(UIColor.red), EKColor(UIColor.orange)], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
        attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.3), scale: .init(from: 1, to: 0.7, duration: 0.7)))
        attributes.shadow = .active(with: .init(color: .black, opacity: 0.5, radius: 10, offset: .zero))
        attributes.statusBar = .light
        attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
        attributes.positionConstraints.maxSize = .init(width: .constant(value: UIScreen.main.bounds.width), height: .intrinsic)

        // UIFont() は任意の形に変更する必要がある        
        let title = EKProperty.LabelContent(text: title, style: .init(font: UIFont(), color: .white))
        let description = EKProperty.LabelContent(text: body, style: .init(font: UIFont(), color: .white))
        let image = EKProperty.ImageContent(image: image, size: CGSize(width: 35, height: 35))
        let simpleMessage = EKSimpleMessage(image: image, title: title, description: description)
        let notificationMessage = EKNotificationMessage(simpleMessage: simpleMessage)

        let contentView = EKNotificationMessageView(with: notificationMessage)
        SwiftEntryKit.display(entry: contentView, using: attributes)
    }
}
```

利用するのはこんな感じ。

```swift
NotificationBanner.show(title: "Notification title", body: "Notification body", image: UIImage(named: "xxx"))
```


## お洒落なアラートを表示する

```swift
struct Alert {
    // ここではアクションが一つの場合
    static func show(title: String, body: String, image: UIImage, action: @escaping () -> Void) {
        var attributes = EKAttributes.topFloat
        attributes.displayDuration = .infinity
        attributes.entryBackground = .gradient(gradient: .init(colors: [EKColor(UIColor.purple), EKColor(UIColor.blue)], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
        attributes.screenBackground = .visualEffect(style: .prominent)
        attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.3), scale: .init(from: 1, to: 0.7, duration: 0.7)))
        attributes.shadow = .active(with: .init(color: .black, opacity: 0.5, radius: 10, offset: .zero))
        attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
        attributes.positionConstraints.maxSize = .init(width: .constant(value: UIScreen.main.bounds.width), height: .intrinsic)
        // これがないとアクションが実行されない
        attributes.entryInteraction = .absorbTouches

        // UIFont() は任意の形に変更する必要がある
        let title = EKProperty.LabelContent(text: title, style: .init(font: UIFont(), color: .white))
        let description = EKProperty.LabelContent(text: body, style: .init(font: UIFont(), color: .white))
        let image = EKProperty.ImageContent(image: image, size: CGSize(width: 35, height: 35))
        let simpleMessage = EKSimpleMessage(image: image, title: title, description: description)        

        let label = EKProperty.LabelContent(text: "OK", style: .init(font: UIFont(), color: .black))
        let button = EKProperty.ButtonContent(label: label, backgroundColor: .white, highlightedBackgroundColor: .standardBackground) {
            // dismissを自分で呼ばないとビューが消えない
            SwiftEntryKit.dismiss()
            action()
        }        
        let content = EKProperty.ButtonBarContent(with: [button], separatorColor: EKColor(UIColor.black), expandAnimatedly: true)

        let alertMessage = EKAlertMessage(simpleMessage: simpleMessage, buttonBarContent: content)
        let view = EKAlertMessageView(with: alertMessage)
        
        SwiftEntryKit.display(entry: view, using: attributes)
    }
}
```

利用するのはこんな感じ。

```swift
Alert.show(title: "Alert title", body: "Alert body") {
    print("tapped!!")
}
```

## ポップアップを表示する

```swift
struct Popup {
    static func show(title: String, body: String, action: @escaping () -> Void) {
        var attributes = EKAttributes.centerFloat
        attributes.displayDuration = .infinity
        attributes.entryBackground = .gradient(gradient: .init(colors: [EKColor(UIColor.purple), EKColor(UIColor.blue)], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
        attributes.screenBackground = .visualEffect(style: .prominent)
        attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.3), scale: .init(from: 1, to: 0.7, duration: 0.7)))
        attributes.shadow = .active(with: .init(color: .black, opacity: 0.5, radius: 10, offset: .zero))
        attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
        attributes.positionConstraints.maxSize = .init(width: .constant(value: UIScreen.main.bounds.width), height: .intrinsic)
        // これがないとアクションが実行されない
        attributes.entryInteraction = .absorbTouches
        // UIFont() は任意の形に変更する必要がある
        let title = EKProperty.LabelContent(text: title, style: .init(font: UIFont(), color: .white))
        let description = EKProperty.LabelContent(text: body, style: .init(font: UIFont(), color: .white))
        
        let label = EKProperty.LabelContent(text: "OK", style: .init(font: UIFont(), color: .black))
        let button = EKProperty.ButtonContent(label: label, backgroundColor: .white, highlightedBackgroundColor: .standardBackground)
        let action: EKPopUpMessage.EKPopUpMessageAction = {
            SwiftEntryKit.dismiss()
            // ここにボタン押下時のアクションを入れることができる
            action()
        }
        let popupMessage = EKPopUpMessage(title: title, description: description, button: button, action: action)
        let view = EKPopUpMessageView(with: popupMessage)
        
        SwiftEntryKit.display(entry: view, using: attributes)
    }
}
```

利用するのはこんな感じ。

```swift
Popup.show(title: "Popup title", body: "Popup body") {
    print("tapped!!")
}
```


## フォーム付きのポップアップを表示する

```swift
struct Form {
    // フォームが1つの場合
    static func show(title: String, placeholder: String = "", image: UIImage, action: @escaping (String) -> Void) {
        var attributes = EKAttributes.centerFloat
        attributes.displayDuration = .infinity
        attributes.entryBackground = .gradient(gradient: .init(colors: [EKColor(UIColor.purple), EKColor(UIColor.blue)], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
        attributes.screenBackground = .visualEffect(style: .prominent)
        attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.3), scale: .init(from: 1, to: 0.7, duration: 0.7)))
        attributes.shadow = .active(with: .init(color: .black, opacity: 0.5, radius: 10, offset: .zero))
        attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
        attributes.positionConstraints.maxSize = .init(width: .constant(value: UIScreen.main.bounds.width), height: .intrinsic)
        // これがないとアクションが実行されない
        attributes.entryInteraction = .absorbTouches

        let white = EKColor(UIColor.white)
        let black = EKColor(UIColor.black)
        let gray = EKColor(UIColor.gray)
        
        let whiteStyle = EKProperty.LabelStyle(font: UIFont(), color: white)
        let blackStyle = EKProperty.LabelStyle(font: UIFont(), color: black)
        
        let title = EKProperty.LabelContent(text: title, style: whiteStyle)
        let ok = EKProperty.LabelContent(text: "OK", style: blackStyle)
        let placeholder = EKProperty.LabelContent(text: placeholder, style: whiteStyle)
        
        let content = EKProperty.TextFieldContent(keyboardType: .asciiCapable, placeholder: placeholder, tintColor: white, displayMode: .inferred, textStyle: whiteStyle, leadingImage: image, bottomBorderColor: white)
        
        let button = EKProperty.ButtonContent(label: ok, backgroundColor: .white, highlightedBackgroundColor: gtray) {
            SwiftEntryKit.dismiss()
            action(content.textContent)
        }
        
        let view = EKFormMessageView(with: title, textFieldsContent: [content], buttonContent: button)
        
        SwiftEntryKit.display(entry: view, using: attributes)
    }
}
```

利用するのはこんな感じ。


```swift
Form.show(title: "Form title", body: "Form body") { value in
    print("input value: \(value)")
}
```

# まとめ

他にもToast、Note、Rating等のビューが用意されているが、同様の要領で利用できる。繰り返しになるが、`EKNotificationMessageView`など、`EKxxxView`を作って`attributes`とともに`SwiftEntryKit`に渡せば良い。