import SwiftUI
struct NeumorphismBootCampAdvanced: View {
let customWhite = Color.init(red: 0.7608050108, green: 0.8164883852, blue: 0.9259157777)
var body: some View {
VStack(alignment: .center, spacing: 60) {
RectangleButtonView()
CircleButton()
PayButton()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(customWhite)
.edgesIgnoringSafeArea(.all)
.animation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0))
}
}
struct RectangleButtonView: View {
let customWhite = Color.init(red: 0.7608050108, green: 0.8164883852, blue: 0.9259157777)
@State private var isTapped: Bool = false
@State private var isPressed: Bool = false
var body: some View {
Text("Button")
.foregroundColor(.black)
.font(.system(size: 20, weight: .semibold, design: .rounded))
.frame(width: 200, height: 60)
.background(
ZStack {
isPressed ? Color.white : customWhite
RoundedRectangle(cornerRadius: 16, style: .continuous)
.foregroundColor(isPressed ? customWhite : Color.white)
.blur(radius: 4)
.offset(x: -8, y: -8)
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(
LinearGradient(gradient: Gradient(colors: [customWhite, .white]), startPoint: .topLeading, endPoint: .bottomTrailing)
)
.padding(2)
.blur(radius: 2)
}
)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.overlay(
HStack {
Image(systemName: "person.crop.circle")
.font(.system(size: 24, weight: .light))
.foregroundColor(Color.white.opacity(isPressed ? 0 : 1))
.frame(width: isPressed ? 64 : 54, height: isPressed ? 4 : 50)
.background(Color.purple)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.purple, radius: 10, x: 10, y: 10)
.offset(x: isPressed ? 70 : -10, y: isPressed ? 16 : 0)
Spacer()
}
)
.shadow(color: isPressed ? Color.white : customWhite, radius: 20, x: 20, y: 20)
.shadow(color: isPressed ? customWhite : Color.white, radius: 20, x: -20, y: -20)
.scaleEffect(isTapped ? 1.2 : 1)
.gesture(
LongPressGesture(minimumDuration: 0.5, maximumDistance: 10)
.onChanged({ value in
isTapped = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.isTapped = false
}
})
.onEnded({ value in
isPressed.toggle()
})
)
}
}
ZStack
겹친 직사각형 뷰를 통해 내/외부 그림자 표현person.crip.circle
이미지 놓기, 프레스 제스처에 따라 위치/형태 변경LongPressGesture
를 통해 탭/프레스 제스처 모두 조정struct CircleButton: View {
let customWhite = Color.init(red: 0.7608050108, green: 0.8164883852, blue: 0.9259157777)
@State private var isTapped: Bool = false
@State private var isPressed: Bool = false
var body: some View {
ZStack {
Image(systemName: "sun.max")
.foregroundColor(.black)
.font(.system(size: 44, weight: .light))
.offset(x: isPressed ? -90 : 0, y: isPressed ? -90 : 0)
.rotation3DEffect(Angle(degrees: isPressed ? 20 : 0), axis: (x: 10, y: -10, z: 0))
Image(systemName: "moon")
.foregroundColor(.black)
.font(.system(size: 44, weight: .light))
.offset(x: isPressed ? 0 : 90, y: isPressed ? 0 : 90)
.rotation3DEffect(Angle(degrees: isPressed ? 0 : 20), axis: (x: -10, y: 10, z: 0))
}
.frame(width: 100, height: 100)
.background(
ZStack {
LinearGradient(gradient: Gradient(colors: isPressed ? [customWhite, .white] : [.white, customWhite]), startPoint: .topLeading, endPoint: .bottomTrailing)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: isPressed ? Color.white : customWhite, radius: 3, x: -5, y: -5)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: isPressed ? customWhite : Color.white, radius: 3, x: 3, y: 3)
}
)
.clipShape(Circle())
.shadow(color: isPressed ? customWhite : .white, radius: 20, x: -20, y: -20)
.shadow(color: isPressed ? .white : customWhite, radius: 20, x: 20, y: 20)
.scaleEffect(isTapped ? 1.2 : 1)
.gesture(
LongPressGesture()
.onChanged({ value in
isTapped = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.isTapped = false
}
})
.onEnded({ value in
isPressed.toggle()
})
)
}
}
ZStack
및 오프셋 y
값을 통해 변경하는 것처럼 보이게 구현struct PayButton: View {
let customWhite = Color.init(red: 0.7608050108, green: 0.8164883852, blue: 0.9259157777)
@GestureState var isTapped: Bool = false
@State var isPressed: Bool = false
var body: some View {
ZStack {
Image("fingerprint_off")
.resizable()
.scaledToFit()
.frame(width: 70, height: 70)
.opacity(isPressed ? 0 : 1)
.scaleEffect(isPressed ? 0 : 1)
Image("fingerprint_on")
.resizable()
.scaledToFit()
.frame(width: 70, height: 70)
.clipShape(Rectangle().offset(y: isTapped ? 10 : 100))
.animation(.easeInOut)
.opacity(isPressed ? 0 : 1)
.scaleEffect(isPressed ? 0 : 1)
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 44, weight: .light))
.foregroundColor(Color.purple)
.opacity(isPressed ? 1 : 0)
.scaleEffect(isPressed ? 1 : 0)
}
.frame(width: 120, height: 120)
.background(
ZStack {
LinearGradient(gradient: Gradient(colors: isPressed ? [customWhite, .white] : [.white, customWhite]), startPoint: .topLeading, endPoint: .bottomTrailing)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: isPressed ? Color.white : customWhite, radius: 3, x: -5, y: -5)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: isPressed ? customWhite : Color.white, radius: 3, x: 3, y: 3)
}
)
.clipShape(Circle())
.overlay(
Circle()
.trim(from: isTapped ? 0.00001 : 1, to: 1)
.stroke(LinearGradient(gradient: Gradient(colors: [.purple, .blue]), startPoint: .topLeading, endPoint: .bottomTrailing), style: StrokeStyle(lineWidth: 5, lineCap: .round))
.frame(width: 88, height: 88)
.rotationEffect(Angle(degrees: 90))
.rotation3DEffect(Angle(degrees: 180), axis: (x: 1, y: 0, z: 0))
.shadow(color: Color.purple.opacity(0.3), radius: 5, x: 3, y: 3)
.animation(.easeInOut)
)
.shadow(color: isPressed ? customWhite : .white, radius: 20, x: -20, y: -20)
.shadow(color: isPressed ? .white : customWhite, radius: 20, x: 20, y: 20)
.scaleEffect(isTapped ? 1.2 : 1)
.gesture(
LongPressGesture()
.updating($isTapped) { currentState, gestureState, transaction in
gestureState = currentState
}
.onEnded({ value in
isPressed.toggle()
})
)
}
}
opacity
를 통해 현재 제스처 상태에 따라서 표현 여부 결정trim
으로 테두리르 자른 원주 표현 → 제스처 상태에 따라서 from
값이 변경, 애니메이션을 통해 차오르는 듯한 이펙트SwiftUI가 UIKit보다 오프셋, 애니메이션, 제스처 사용이 보다 편리하다보니 화려한 애니메이션 또한 구현이 쉬운 것 같다. ZStack 등 겹친 이미지, 그레디언트 역시 보다 편리하니 말이다.