13
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【SwiftUI】通知バナーのようなUIを作ってみた

Last updated at Posted at 2021-12-12

バナーを出したい

こんな感じの記述方法で簡単に画面上部にスライドインでバナーを出したい

View
  .bannerVisible(with: Binding<Bool>) 

できたもの

作ってみた

大まかな手順はこの通りです
① Bannar表示用のModifierを作る
② ①で作ったModifierを簡単に使えるようにViewのExtensionを作成する

Modifierを作る

struct Banner: ViewModifier {
    @Binding var isShow: Bool
    let title: String
    let icon: String
    let foregroundColor: Color
    let backgroundColor: Color

    @State private var offset: CGFloat = -100
    @State private var opacity: CGFloat = 0

    func body(content: Content) -> some View {
        let asyncHide = DispatchWorkItem() { hide() }

        ZStack {
            content
            if isShow {
                VStack {
                    bannerView
                    Spacer()
                }
                .padding()
                .onTapGesture {
                    asyncHide.cancel()
                    hide()
                }
                .onAppear {
                    show()
                    DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: asyncHide)
                }
                .offset(y: offset)
            }
        }
    }

    private var bannerView: some View {
        HStack {
            Image(systemName: icon)
                .opacity(opacity)
            Text(title).bold()
                .opacity(opacity)
            Spacer()
        }
        .foregroundColor(foregroundColor)
        .padding(12)
        .background(backgroundColor.opacity(opacity))
        .cornerRadius(8)
    }

    private func show() {
        withAnimation(.easeInOut(duration: 0.3)) {
            offset = 0
            withAnimation(.easeInOut(duration: 0.45)) {
                opacity = 0.75
            }
        }
    }

    private func hide() {
        withAnimation(.easeInOut(duration: 0.3)) {
            offset = -100
            withAnimation(.easeInOut(duration: 0.2)) {
                opacity = 0
                self.isShow = false
            }
        }
    }
}

.animation(_ animation:)がiOS15.0でdeprecatedになるため、show()hide()で独自でoffsetを変更することで上からバナーが出てくるアニメーションを実現しています。
バナーを表示するためにisShowの値を変えるときに、withAnimation { isShow = true } と記述すれば独自でoffsetを変更させる必要はないのですが、単にisShow = true とシンプルに記述したいのでこのようにしました。

Viewのextensionを作る

extension View {
    func bannerVisible(with isShown: Binding<Bool>,
                       title: String = "TEST",
                       icon: String = "star.fill",
                       foregroundColor: Color = .white,
                       backgroundColor: Color = .red) -> some View {
        self.navigationBarTitleDisplayMode(.inline)
            .modifier(Banner(isShown: isShown,
                             title: title,
                             icon: icon,
                             foregroundColor: foregroundColor,
                             backgroundColor: backgroundColor
                            )
            )
    }
}

ある程度バナーの色などをカスタムできるようにしました。
また,NavigationViewの中で使うとうまく画面トップにバナーが出なかったため
.navigationTitleDisplayMode(.inline)にして期待通りに位置にバナーを表示できるようにしました。

使い方

struct ContentView: View {
    @State private var isShow: Bool = false

    var body: some View {
        VStack {
            Button(action: { isShow = true }) {
                Text("バナー表示")
            }
        }
        .bannerVisible(with: $isShow)
    }
}

このようにバナーを表示したいViewの最上層のViewに.bannerVisibleを追加すれば使用できます。

参考

バナーで検索すると広告についての記事ばかりが出てきましたが
この記事を見つけられて良かったです。

↑の記事で作成したModifierを.bannerVisibleと記述すれば使えるようにする方法を探してこの記事に辿り着きました。

13
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?