Skip to content

Instantly share code, notes, and snippets.

@AdamWhitcroft
Last active February 1, 2023 18:33
Show Gist options
  • Save AdamWhitcroft/8d41e2c500a499e76540e2db3cc684a6 to your computer and use it in GitHub Desktop.
Save AdamWhitcroft/8d41e2c500a499e76540e2db3cc684a6 to your computer and use it in GitHub Desktop.
/*
Design credit to Nev Flynn
https://twitter.com/NevFlynn/status/1620426055155859458
*/
struct NevButton: View {
let pageBackground = Color(#colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1))
var body: some View {
ZStack {
pageBackground
.edgesIgnoringSafeArea(.all)
VStack(spacing: 8) {
HStack(spacing: 8) {
NevButtonButton(
buttonContent: "1",
buttonImageSystemName: "circle",
buttonImageColorName: .red
)
NevButtonButton(
buttonContent: "2",
buttonImageSystemName: "diamond",
buttonImageColorName: .blue
)
}
HStack {
NevButtonButton(
buttonContent: "3",
buttonImageSystemName: "square",
buttonImageColorName: .yellow
)
NevButtonButton(
buttonContent: "4",
buttonImageSystemName: "circle",
buttonImageColorName: .black
)
}
}
}
}
}
struct NevButtonButton: View {
@State private var buttonPressed = false
let buttonContent: String
let buttonImageSystemName: String
let buttonImageColorName: Color
var body: some View {
Button {
buttonPressed.toggle()
Haptics.shared.play(.soft)
SoundManager.instance.playSound(sound: .click)
} label: {}
.buttonStyle(
NevButtonStyle(
buttonPressed: $buttonPressed,
buttonContentString: buttonContent,
buttonImageSystemName: buttonImageSystemName,
buttonImageColorName: buttonImageColorName
)
)
}
}
struct NevButtonStyle: ButtonStyle {
@Binding var buttonPressed: Bool
let buttonContentString: String
let buttonImageSystemName: String
let buttonImageColorName: Color
let buttonGradientTopDefault = Color(#colorLiteral(red: 0.8784313725, green: 0.8784313725, blue: 0.8784313725, alpha: 1))
let buttonGradientBottomDefault = Color(#colorLiteral(red: 0.9921568627, green: 0.9921568627, blue: 0.9921568627, alpha: 1))
let buttonRoundShapeGradientTop = Color(#colorLiteral(red: 0.9176470588, green: 0.9176470588, blue: 0.9176470588, alpha: 1))
let buttonRoundShapeGradientBottom = Color(#colorLiteral(red: 0.9333333333, green: 0.9333333333, blue: 0.9333333333, alpha: 1))
let animation: Animation = .interpolatingSpring(mass: 0.2, stiffness: 300, damping: 20)
func makeBody(configuration: Configuration) -> some View {
ZStack {
// indented surround
Rectangle()
.fill(LinearGradient(colors: [buttonGradientTopDefault, buttonGradientBottomDefault], startPoint: .top, endPoint: .bottom))
.frame(width: 120, height: 120)
.clipShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
.shadow(color: .white, radius: 1, x: 0, y: 1)
// button base
Rectangle()
.fill(LinearGradient(colors: [buttonGradientBottomDefault, buttonGradientTopDefault], startPoint: .top, endPoint: .bottom))
.frame(width: 88, height: 88)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(
color: buttonPressed == true ? .black.opacity(0) : .black.opacity(0.1),
radius: buttonPressed == true ? 0 : 8,
x: 0,
y: buttonPressed == true ? 0 : 4
)
.shadow(
color: buttonPressed == true ? .black.opacity(0) : .black.opacity(0.125),
radius: buttonPressed == true ? 0 : 6,
x: 0,
y: buttonPressed == true ? 0 : 3
)
.shadow(
color: buttonPressed == true ? .black.opacity(0) : .black.opacity(0.2),
radius: buttonPressed == true ? 0 : 1,
x: 0,
y: buttonPressed == true ? 0 : 1
)
// button inner square
.overlay {
Rectangle()
.fill(
LinearGradient(
colors: [buttonGradientTopDefault, buttonGradientBottomDefault],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: 76, height: 76)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
}
.animation(animation, value: buttonPressed)
// button outline
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(
LinearGradient(
colors: buttonPressed == true ? [.black.opacity(0.15), .black.opacity(0.15)] : [.black.opacity(0.2), .black.opacity(0.25)],
startPoint: .top,
endPoint: .bottom),
lineWidth: 1
)
.background(.clear)
.frame(width: 88, height: 88)
// button inner circle
Circle()
.fill(
LinearGradient(
colors: [buttonRoundShapeGradientTop, buttonRoundShapeGradientBottom],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: 64, height: 64)
// button symbol
Image(systemName: buttonPressed == true ? "\(buttonImageSystemName).fill" : buttonImageSystemName)
.foregroundColor(buttonPressed == true ? buttonImageColorName : .gray)
.shadow(color: .white, radius: 0, x: 0, y: 1)
.font(.system(.body, design: .rounded).weight(.bold))
.animation(.easeIn(duration: 0.125), value: buttonPressed)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment