본문 바로가기

IOS

SwiftUI, 아이콘전환 인터렉티브 애니메이션(shape morphing)

728x90
반응형

결과물🤟🏻: 

아이콘 전환시 부드럽게 넘어가는 느낌의 애니메이션

result.gif

이번 애니메이션을 학습하게된 계기 🔫:

이전 포스팅에서 다이나믹 아일랜드와 이미지가 합쳐지는 듯한 효과를 학습했었다.

https://april21st.tistory.com/185

Kavsoft채널에서 Metaball Effect로 소개된 애니메이션이며 Cavas shape과 blur효과를 적절히 사용한 것이다. Metaball Effect와 비슷한 방식으로 Blur효과를 적절히 사용하여 아이콘 전환 애니메이션을 학습 및 탐구

Reference ✂️ : 

https://www.youtube.com/watch?v=HVNxfI8XYMw 

 

과정 🎩:

Step 1. 아이콘(이미지) View 설정

struct ResolvedImage: View {
    @Binding var currentImage: CustomShape
    var body: some View {
        Image(systemName: currentImage.rawValue)
            .font(.system(size:200))
            .animation(.interactiveSpring(response: 0.7, dampingFraction: 0.8, blendDuration: 0.8), value: currentImage)
            .frame(width: 300, height: 300)
    }
}

아이콘들을 표시할 View를 struct로 구성하며 이미지의 Modifier를 구성

animation의 Spring은 피사체가 이동 후 반동을 주는 효과를 적용시킬 수 있다. 

Spring 애니메이션을 약간 딱딱한 느낌으로 사용한 것이 interativeSpring이다.

Animate 버튼 클릭시 미적용

Update 버튼 클릭시 애니메이션 적용

(response: 반응속도, dampingFriction: 제동력, blendDuration: 두 개 이상의 SpringAnimation을 적용했을 때의 혼합 정도)

spring.gif

 

interativeSpring.gif

 

// CustomSahpe Model

import SwiftUI

enum CustomShape: String, CaseIterable{
    case cloud = "cloud.rain.fill"
    case bubble = "bubble.left.and.bubble.right.fill"
    case map = "map.fill"
    case square_1 = "square.fill.on.square.fill"
    case bell = "bell.and.waves.left.and.right.fill"
    case square = "square.fill.on.circle.fill"
}

CustomShape는 열거형(enum)으로 모델구성을한다. 화면 전환시 보여질 아이콘들을 열거하였다.

Step 2. MainView에 보여질 아이콘(이미지) 설정

Canvas{context, size in
        // MARK: Morphing
        context.addFilter(.alphaThreshold(min: 0.3))
        // MARK: This Value Plays Major Role in the Morphing Animation
        // MARK: For Reverse Animation
        context.addFilter(.blur(radius: blurRadius >= 20 ? 20 - (blurRadius - 20) : blurRadius))                            
        // MARK: Draw Inside Layer
        context.drawLayer { ctx in
            if let resolvedImage = context.resolveSymbol(id: 1){
                ctx.draw(resolvedImage, at: CGPoint(x: size.width / 2, y: size.height / 2), anchor: .center)
            }
        }

    } symbols: {
        // MARK: Giving Images With ID
        ResolvedImage(currentImage: $currentImage)
            .tag(1)
}

Canvas를 활용하여 ResolvedImage를 그려준다. 또한 blurRadius를 40으로 설정하여 1에서 시작하여 20까지는 사라지는 느낌의 효과 21부터 40까지는 다시 생성되는 효과를 줄 수 있도록 한다.

// MARK: Animation will not Work in the Convas
.onReceive(Timer.publish(every: 0.007, on: .main, in: .common).autoconnect()) { _ in
    if animateMorph {
        if blurRadius <= 40 {
            blurRadius += 0.5

            if blurRadius.rounded() == 20 {
                // MARK: Change Of Next Image Goes Here
                currentImage = pickerImage
            }
        }

        if blurRadius.rounded() == 40 {
            // MARK: End Animation And Reset the Blur Radius to Zero
            animateMorph = false
            blurRadius = 0
        }
    }
}

 

Step 3. 아이콘 전환 선택 활성화

//MARK: Segmented Picker
Picker("", selection: $pickerImage) {
    ForEach(CustomShape.allCases, id: \.rawValue) { shape in
        Image(systemName: shape.rawValue)
            .tag(shape)
    }
}
.pickerStyle(.segmented)

// MARK: Avoid Tap Until The Current Animation is Finished
.overlay(content: {
    Rectangle()
        .fill(.primary)
        .opacity(animateMorph ? 0.05 : 0)
})
.padding(15)
.padding(.top, -50)

// MARK: When Ever picker Image Changes    
.onChange(of: pickerImage) { newValue in
    animateMorph = true
}

Picker를 사용하여 아이콘(이미지)를 선택할 수 있도록 설정 또한 Retangle을 overlay로 설정하여 아이콘 전환 중에 다른 버튼을 클릭할 수 없도록 방지하며 아이콘 선택창이 활성화된 것을 사용자가 인식할 수 있도록 한다.

Step 4. 토글버튼을 사용하여 아이콘 화면에 다른 이미지(사진)을 표시할 수 있는 효과 설정

Toggle("Turn Off Image Morph", isOn: $turnOffImageMorph)
    .fontWeight(.semibold)
    .padding(.horizontal, 15)
    .padding(.top, 10)

turnOffImageMorph가 true인 경우 다른 이미지(사진)이 mask된다.

Step 5. 이미지(사진)을 설정

GeometryReader { proxy in
    let size = proxy.size
    Image("pic")
        .resizable()
        .aspectRatio(contentMode: .fill)
        .offset(y: 50)
        .frame(width: size.width, height: size.height)
        .clipped()
        .overlay(content: {
            Rectangle()
                .fill(.white)
                .opacity(turnOffImageMorph ? 1: 0)

        })
        .mask{
            ...
            }
}

turnOfImageMorph 토글을 활용하여 투명도를 1과 0으로 설정하여 이미지 노출을 설정한다. 

.mask 블럭안에는 Canvas블럭을 넣어준다.

mask는 ZStack처럼 그림위에 아이콘을 입힐 수 있도록 활용하는 것이다.

maskExample.jpg

 

마스크를 활용하면 별점 효과를 효과적으로 만들 수 있다. (서근 개발 노트 참고)

maskExample.gif

https://seons-dev.tistory.com/entry/SwiftUI-Mask

 

반응형