Skip to content

Instantly share code, notes, and snippets.

@fespinoza
Created January 9, 2022 20:36

Revisions

  1. fespinoza created this gist Jan 9, 2022.
    138 changes: 138 additions & 0 deletions example.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,138 @@
    import SwiftUI

    /*

    ## Animated highlight over redacted code

    To actually try to get a loading shimmer animation on redacted content

    Interesting concepts:
    - overlay + `.blendMode`
    - Infinite Animations
    - Stopping animations

    Sources:
    - https://www.swiftkickmobile.com/creating-a-repeating-animation-in-swiftui/
    - https://stackoverflow.com/questions/59133826/swiftui-stop-an-animation-that-repeats-forever

    */

    struct RedactedAnimationExperimentView: View {
    @State var offsetX: CGFloat = 0
    @State var duration: CGFloat = 1.5
    @State var animationStarted: Bool = false
    @State var stopAnimation: Bool = false

    // var animation: Animation? = Animation.easeInOut.repeatForever(autoreverses: false)

    var body: some View {
    VStack {
    SampleContentView()
    .redacted(reason: .placeholder)
    .overlay(overlayContent)

    Circle()
    .foregroundColor(Color.gray)
    .frame(width: 200, height: 200)
    .overlay(overlayContent)
    .clipped()

    Spacer()

    form
    }
    }

    var overlayContent: some View {
    GeometryReader { proxy in
    Color
    .white
    .opacity(0.5)
    // .blur(radius: 5)
    .frame(width: proxy.size.width * 0.3)
    .offset(x: proxy.size.width * offsetX, y: 0)
    .blendMode(.lighten)
    }
    }

    var form: some View {
    VStack {
    Text("Offset: ") + Text(offsetX, format: .number)

    Text("Duration: ") + Text(duration, format: .number)

    Slider(value: $offsetX)
    .padding()

    Slider(value: $duration, in: (0.1...CGFloat(3.0)))
    .padding()

    Button("Animate", action: animateOverlay)
    }
    }

    let delta: CGFloat = 5

    func animateOverlay() {
    var animation = Animation.easeInOut(duration: duration)

    if animationStarted {
    // animationRunningFromBefore = false
    // no repeat forever
    animationStarted = false
    } else {
    animation = animation.repeatForever(autoreverses: false)
    animationStarted = true
    }

    withAnimation(animation) {
    offsetX = offsetX > 1 ? 0 : offsetX + delta
    }
    }
    }

    extension RedactedAnimationExperimentView {
    struct SampleContentView: View {
    var body: some View {
    VStack {
    Image("sampleAvatar")
    .resizable()
    .frame(width: 80, height: 80)
    .clipShape(Circle())
    .clipped()

    Text("Felipe Espinoza")
    .font(.title)

    Divider()

    HStack {
    number(title: "Followers", value: "20")
    Divider().frame(height: 80)
    number(title: "Following", value: "10")
    }

    Divider()

    // Spacer()
    }
    }

    @ViewBuilder func number(title: String, value: String) -> some View {
    Spacer()

    VStack(spacing: 8) {
    Text(title).font(.subheadline)
    Text(value).font(.title2)
    }

    Spacer()
    }
    }
    }

    struct RedactedAnimationExperimentView_Previews: PreviewProvider {
    static var previews: some View {
    RedactedAnimationExperimentView()
    }
    }