Codementor Events

SwiftUI - Viber splash screen with animations

Published Jul 17, 2022
SwiftUI - Viber splash screen with animations

If you read my previous stories, that explain how to build custom OTP view in couple parts, you might come to conclusion that we are building UI similar to iOS Viber Messenger application. Well, that is correct 😃

In this short article we are going to build Viber look-alike splash screen with viber logo simple animation. If you have not, please check my stories for building custom OTP view.

Now lets start.

Design of screen if pretty simple. As you can see in next gist, nothing special.

import SwiftUI

struct SplashView: View {
    //MARK: vars
    @State private var move = false
    
    //MARK: body
    var body: some View {
        NavigationView {
            ZStack {
                Color.colorBackground.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
                VStack() {
                    VStack {
                        Image("rakuten.splash.logo")
                            .resizable()
                            .frame(width: 120, height: 30, alignment: .center)
                            .padding(.top, 25)
                        ZStack {
                            Image("viber.splash.logo.base")
                                .resizable()
                                .frame(width: 105, height: 115, alignment: .center)
                                .padding(.top, 59)
                            
                            Image("viber.splash.logo.circle")
                                .resizable()
                                .frame(width: 120, height: 115, alignment: .center)
                                .padding(.top, 59)
                                .scaleEffect(move ? 1 : 0)
                                .offset(x: move ? 0 : -50, y: move ? 0 : 50)
                                .animation(.easeIn(duration: 0.5).repeatCount(1, autoreverses: false), value: move)
                            
                            Image("viber.splash.logo.phone")
                                .resizable()
                                .frame(width: 105, height: 115, alignment: .center)
                                .padding(.top, 50)
                                .scaleEffect(move ? 1 : 0)
                                .offset(x: move ? 0 : -50, y: move ? 0 : 50)
                                .animation(.easeIn(duration: 0.4).repeatCount(1, autoreverses: false).delay(0.5), value: move)
                            
                            Image("viber.splash.logo.line1")
                                .resizable()
                                .frame(width: 105, height: 115, alignment: .center)
                                .padding(.top, 35)
                                .padding(.leading, 12)
                                .scaleEffect(move ? 1 : 0)
                                .offset(x: move ? 0 : -50, y: move ? 0 : 50)
                                .animation(.easeIn(duration: 0.4).repeatCount(1, autoreverses: false).delay(0.7), value: move)
                            
                            Image("viber.splash.logo.line2")
                                .resizable()
                                .frame(width: 105, height: 115, alignment: .center)
                                .padding(.top, 28)
                                .padding(.leading, 22)
                                .scaleEffect(move ? 1 : 0)
                                .offset(x: move ? 0 : -50, y: move ? 0 : 50)
                                .animation(.easeIn(duration: 0.5).repeatCount(1, autoreverses: false).delay(0.8), value: move)
                            
                            Image("viber.splash.logo.line3")
                                .resizable()
                                .frame(width: 105, height: 115, alignment: .center)
                                .padding(.top, 22)
                                .padding(.leading, 28)
                                .scaleEffect(move ? 1 : 0)
                                .offset(x: move ? 0 : -50, y: move ? 0 : 50)
                                .animation(.easeIn(duration: 0.6).repeatCount(1, autoreverses: false).delay(0.9), value: move)
                        }
                        
                        Text(LocalizedStringKey("welcome.viber"))
                            .font(Font.system(size: 25))
                            .fontWeight(.bold)
                            .foregroundColor(Color.white)
                            .padding(.top, 40)
                        
                        Text(LocalizedStringKey("welcome.viber.description"))
                            .font(Font.system(size: 16))
                            .fontWeight(.medium)
                            .foregroundColor(Color.white)
                            .lineLimit(2)
                            .multilineTextAlignment(.center)
                            .padding(1)
                    }
                    Spacer()
                    VStack {
                        HStack(spacing: 2){
                            Text(LocalizedStringKey("read.policy"))
                                .font(Font.system(size: 12))
                                .foregroundColor(Color.white)
                            Link(destination: URL(string: "https://www.apple.com")!, label: {
                                Text(LocalizedStringKey("privacy.policy"))
                                    .font(Font.system(size: 12))
                                    .foregroundColor(Color.white)                                    .underline()
                            })
                        }
                        HStack(spacing: 2){
                            Text(LocalizedStringKey("tapping.policies"))
                                .font(Font.system(size: 12))
                                .foregroundColor(Color.white)
                            Link(destination: URL(string: "https://www.apple.com")!, label: {
                                Text(LocalizedStringKey("terms.policies"))
                                    .font(Font.system(size: 12))
                                    .foregroundColor(Color.white)
                                    .underline()
                            })
                        }
                    }
                    
                    Button(action: {
                        
                    }) {
                        Text(LocalizedStringKey("start.now"))
                            .foregroundColor(Color.textColor)
                            .font(Font.system(size: 17))
                            .fontWeight(.medium)
                            .frame(maxWidth: .infinity)
                    }
                    .padding(11)
                    .foregroundColor(Color.white)
                    .background(Color.white)
                    .cornerRadius(50)
                    .padding(.top, 10)
                    .padding(.leading, 35)
                    .padding(.trailing, 35)
                    .padding(.bottom, 33)
                }
            }
            .onAppear {
                move.toggle()
            }
            .navigationBarHidden(true)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct SplashView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            SplashView().environment(\.colorScheme, .light).previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
            SplashView().environment(\.colorScheme, .light).previewDevice(PreviewDevice(rawValue: "iPhone 8"))
            SplashView().environment(\.colorScheme, .light).previewDevice(PreviewDevice(rawValue: "iPhone 11"))
            SplashView().environment(\.colorScheme, .light).previewDevice(PreviewDevice(rawValue: "iPhone 12"))
            SplashView().environment(\.colorScheme, .light).previewDevice(PreviewDevice(rawValue: "iPhone 13"))
        }
    }
}

The most important thing is animation. We need to break this animation into few parts. As we can see on first image, we need 5 moving parts.

First part is the base image of viber logo that needs to be static.

Image("viber.splash.logo.base")
        .resizable()
        .frame(width: 105, height: 115, alignment: .center)
        .padding(.top, 59)

Next we have 5 parts that are moving, logo circle, logo phone and three curved lines one by one. All of these images are combined into ZStack.

For each of these images, we will apply scaling with offset movement with easeIn animation. The only difference is duration of animation for each image and delay of animation. Delay will be applied for phone logo and three moving curved lines as circle logo is first image that we are animating.

Image("viber.splash.logo.circle")
    .resizable()
    .frame(width: 120, height: 115, alignment: .center)
    .padding(.top, 59)
    .scaleEffect(move ? 1 : 0)
    .offset(x: move ? 0 : -50, y: move ? 0 : 50)
    .animation(.easeIn(duration: 0.5).repeatCount(1, autoreverses: false), value: move)

Image("viber.splash.logo.phone")
    .resizable()
    .frame(width: 105, height: 115, alignment: .center)
    .padding(.top, 50)
    .scaleEffect(move ? 1 : 0)
    .offset(x: move ? 0 : -50, y: move ? 0 : 50)
    .animation(.easeIn(duration: 0.4).repeatCount(1, autoreverses: false).delay(0.5), value: move)

Image("viber.splash.logo.line1")
    .resizable()
    .frame(width: 105, height: 115, alignment: .center)
    .padding(.top, 35)
    .padding(.leading, 12)
    .scaleEffect(move ? 1 : 0)
    .offset(x: move ? 0 : -50, y: move ? 0 : 50)
    .animation(.easeIn(duration: 0.4).repeatCount(1, autoreverses: false).delay(0.7), value: move)

Image("viber.splash.logo.line2")
    .resizable()
    .frame(width: 105, height: 115, alignment: .center)
    .padding(.top, 28)
    .padding(.leading, 22)
    .scaleEffect(move ? 1 : 0)
    .offset(x: move ? 0 : -50, y: move ? 0 : 50)
    .animation(.easeIn(duration: 0.5).repeatCount(1, autoreverses: false).delay(0.8), value: move)

Image("viber.splash.logo.line3")
    .resizable()
    .frame(width: 105, height: 115, alignment: .center)
    .padding(.top, 22)
    .padding(.leading, 28)
    .scaleEffect(move ? 1 : 0)
    .offset(x: move ? 0 : -50, y: move ? 0 : 50)
    .animation(.easeIn(duration: 0.6).repeatCount(1, autoreverses: false).delay(0.9), value: move)

And the final result is:
1_9LiAOlUhXynLpVHco_CJ5g.gif

That is it, hope you have enjoyed this short tutorial, and stay tuned for more content.

As always, code is available on GitHub repository.

Discover and read more posts from Kenan Begić
get started
post comments1Reply
MAYU Technologies
2 years ago

That’s really informative post. I think it would be greatly beneficial in the future who are more interested in mobile apps development for their business growth. I appreciate thanks for sharing.