Notification Banner using SwiftUI
By: Jean-Marc Boullianne
Tags: Tutorials

Notification Banner using SwiftUI

Jean-Marc Boullianne

Getting Started

Begin by creating a new blank project in Xcode. Select the Single View Template, and make sure you set your project to use SwiftUI.

ViewModifier

Instead of creating a custom SwiftUI View today, we'll be creating a ViewModifier . A ViewModifier From the Documentation, "A modifier that you apply to a view or another view modifier, producing a different version of the original value." Essentially at the end of this, we're going to be able to add a banner to any view we want throughout our app with a single line.

BannerModifier

Start off by creating a new SwiftUI ViewModifier file and name it BannerModifier.swift

Just above the body variable, go ahead and define a new struct called BannerData . Inside define two variables, title and detail . These will be used as the text for your banner.

struct BannerData {

var title:String

var detail:String

}

Now declare an instance of BannerData above your body function. For this variable we're going to make it @Binding . This will allow data of the banner to change over time. Thus being able to change the title and details.

@Binding var data:BannerData

Constructing The Body

Since this banner needs to display over all other content on screen when displayed, we’ll need to utilize a ZStack . This will allow us to place all of the content from the body function below the views for our banner.

ZStack

Within the body closure, delete the placeholder that was generated for you and add in a ZStack . Inside place the content and VStack for the banner.

ZStack {

VStack {

// Banner Content Here

Spacer()

}

content

}

The inner VStack and Spacer will allow the banner content to be pushed to the top of the screen.

Banner Layout

Inside the VStack , place a second VStack . Inside the inner one, place two Text views. Use the variables declared inside data as input for both Text views. Go ahead and also give the two Text views some styling. Here's what your body should look like.

func body(content: Content) -> some View {

ZStack {

VStack {

// Banner Content Here

VStack(alignment: .leading, spacing: 2) {

Text(data.title)

.bold()

Text(data.detail)

.font(Font.system(size: 15, weight: Font.Weight.light, design: Font.Design.default))

}



Spacer()

}

content

}

}

I’ll quickly add a HStack in order for our banner to take up the full width of the screen when shown.

func body(content: Content) -> some View {

ZStack {

VStack {

HStack {

// Banner Content Here

VStack(alignment: .leading, spacing: 2) {

Text(data.title)

.bold()

Text(data.detail)

.font(Font.system(size: 15, weight: Font.Weight.light, design: Font.Design.default))

}

Spacer()

}



Spacer()

}

content

}

}

Then to make this banner look a little better, we’ll add some styling to the inner VStack as such:

VStack(alignment: .leading, spacing: 2) {

// Text Views From Before...

}

.foregroundColor(Color.white)

.padding(12)

.background(Color(red: 67/255, green: 154/255, blue: 215/255))

.cornerRadius(8)

What We Have So Far

The Banner we have so far.

Toggling Display of The Banner

Next we’ll add a show variavle which will toggle the display of our banner. When this variable changes value, the banner will hide/display at the top of the device. First define it above the body .

@Binding var show:Bool

Then wrap the outer VStack in the conditional statement.

func body(content: Content) -> some View {

ZStack {

if show {

VStack {

HStack {

VStack(alignment: .leading, spacing: 2) {

Text(data.title)

.bold()

Text(data.detail)

.font(Font.system(size: 15, weight: Font.Weight.light, design: Font.Design.default))

}

Spacer()

}

.foregroundColor(Color.white)

.padding(12)

.background(data.level.tintColor)

.cornerRadius(8)

Spacer()

}

}

content

}

}

Extending View

WE’re going to define an extension to View that way the BannerModifier can be added to any View , in addition to letting us bind our data and display variables. See below for how I did it.

extension View {

func banner(data: Binding<BannerModifier.BannerData>, show: Binding<Bool>) -> some View {

self.modifier(BannerModifier(data: data, show: show))

}

}

Changing the Banner Color

For our banner, we’re going to have four distinct types. Info , Success , Warning , and Error . This will allow us to set four different colors depending on the type of banner being displayed.

To make this easy, we’re going to be using enumerations. Start by defining one named BannerType and create cases for the four I mentioned above.

enum BannerType {

case Info

case Warning

case Success

case Error

}

Now to allow us to get a specific color based on the case, you need to add a variable for color, tintColor .

enum BannerType {

case Info

case Warning

case Success

case Error



var tintColor: Color {

switch self {

case .Info:

return Color(red: 67/255, green: 154/255, blue: 215/255)

case .Success:

return Color.green

case .Warning:

return Color.yellow

case .Error:

return Color.red

}

}

}

To keep track of the BannerType we need to add a instance of it in our BannerData struct.

struct BannerData {

var title:String

var detail:String

var type: BannerType

}

…and to change the banner background color based on the BannerType we change code we defined earlier in our body func.

.background(data.type.tintColor)

Running A Live Example

Let’s put to use what we have so far and show a live example of the banner. Go ahead copy the below code as your ContentView in your main view file.

struct ContentView: View {



@State var showBanner:Bool = true

@State var bannerData: BannerModifier.BannerData = BannerModifier.BannerData(title: "Default Title", detail: "This is the detail text for the action you just did or whatever blah blah blah blah blah", type: .Info)



var body: some View {

Text("Hello Trailing Closure")

.banner(data: $bannerData, show: $showBanner)

}

}

Activating Banner With a Button

Simply replace the Text view with a button as such:

var body: some View {

Button(action: {

self.showBanner = true

}) {

Text("[ Show ]")

}.banner(data: $bannerData, show: $showBanner)

}

Animating The Banner

We want the banner to slide down onto screen and then slip away after a certain time or when the user taps the banner. In order to do so we need to add some code to the banner’s VStack . See the updated body func of BannerModifier

func body(content: Content) -> some View {

ZStack {

if show {

VStack {

HStack {

VStack(alignment: .leading, spacing: 2) {

Text(data.title)

.bold()

Text(data.detail)

.font(Font.system(size: 15, weight: Font.Weight.light, design: Font.Design.default))

}

Spacer()

}

.foregroundColor(Color.white)

.padding(12)

.background(data.type.tintColor)

.cornerRadius(8)

Spacer()

}

.padding()

.animation(.easeInOut)

.transition(AnyTransition.move(edge: .top).combined(with: .opacity))

.onTapGesture {

withAnimation {

self.show = false

}

}.onAppear(perform: {

DispatchQueue.main.asyncAfter(deadline: .now() + 4) {

withAnimation {

self.show = false

}

}

})

}

content

}

}

Notice the .animation() and .transition() functions. These slide the view down from the top when it's shown as well as changing the opacity.

Then just below .onTapGesture() and .onAppear() hide the banner hwne the user taps on it or afte a pre determined amount of time.

Running The Final Product