Skip to content

Instantly share code, notes, and snippets.

@CodeSlicing
Last active February 12, 2022 20:51
Show Gist options
  • Save CodeSlicing/42fec8137563c9cd7a38d6e2079d109c to your computer and use it in GitHub Desktop.
Save CodeSlicing/42fec8137563c9cd7a38d6e2079d109c to your computer and use it in GitHub Desktop.
Native source code for CodeSlicing episode on creating the new playing equaliser bars overlay - part 2
//
// NowPlayingEqualiserBarsUsageDemoNative.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright © 2021 Adam Fordyce. All rights reserved.
//
import SwiftUI
struct NowPlayingEqualiserBarsUsageDemoNative: View {
@State private var nowPlayingIndex = -1
var body: some View {
let numItems = 20
let opacityDeltaPerItem = 1 / Double(numItems)
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
ForEach(0..<numItems) { index in
let isNowPlaying = index == nowPlayingIndex
Button {
withAnimation(.easeInOut) {
if isNowPlaying {
nowPlayingIndex = -1
} else {
nowPlayingIndex = index
}
}
} label: {
ZStack(alignment: .bottomLeading) {
Rectangle()
.fill(Color.blue.opacity(opacityDeltaPerItem + opacityDeltaPerItem * Double(index)))
.frame(height: 120)
if isNowPlaying {
NowPlayingEqualiserBars()
.frame(width: 20, height: 20)
.padding()
.shadow(radius: 1)
.zIndex(1)
.transition(AnyTransition.scale.combined(with: .opacity))
}
}
}
}
}
}
}
private struct NowPlayingEqualiserBars: View {
var numBars = 5
var spacerWidthRatio: CGFloat = 0.2
private var barWidthScaleFactor: CGFloat {
1 / (CGFloat(numBars) + CGFloat(numBars - 1) * spacerWidthRatio)
}
@State private var animating = false
var body: some View {
GeometryReader { (geo: GeometryProxy) in
let barWidth = geo.size.width * barWidthScaleFactor
let spacerWidth = barWidth * spacerWidthRatio
HStack(spacing: spacerWidth) {
ForEach(0..<numBars) { index in
Bar(minHeightFraction: 0.1, maxHeightFraction: 1, completion: animating ? 1 : 0)
.fill(Color.white)
.frame(width: barWidth)
.animation(createAnimation())
}
}
}
.onAppear {
animating = true
}
}
private func createAnimation() -> Animation {
Animation
.easeInOut(duration: 0.8 + Double.random(in: -0.3...0.3))
.repeatForever(autoreverses: true)
.delay(Double.random(in: 0...0.75))
}
}
private struct Bar: Shape {
private let minHeightFraction: CGFloat
private let maxHeightFraction: CGFloat
var animatableData: CGFloat
init(minHeightFraction: CGFloat, maxHeightFraction: CGFloat, completion: CGFloat) {
self.minHeightFraction = minHeightFraction
self.maxHeightFraction = maxHeightFraction
self.animatableData = completion
}
func path(in rect: CGRect) -> Path {
var path = Path()
let heightFractionDelta = maxHeightFraction - minHeightFraction
let heightFraction = minHeightFraction + heightFractionDelta * animatableData
let rectHeight = rect.height * heightFraction
let rectOrigin = CGPoint(x: rect.minX, y: rect.maxY - rectHeight)
let rectSize = CGSize(width: rect.width, height: rectHeight)
let barRect = CGRect(origin: rectOrigin, size: rectSize)
path.addRect(barRect)
return path
}
}
struct NowPlayingEqualiserBarsUsageDemoNative_Previews: PreviewProvider {
struct NowPlayingEqualiserBarsUsageDemoNative_Harness: View {
var body: some View {
NowPlayingEqualiserBarsUsageDemoNative()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundColor(.white)
.background(Color(white: 0.1))
.ignoresSafeArea()
}
}
static var previews: some View {
NowPlayingEqualiserBarsUsageDemoNative_Harness()
.previewDevice("iPhone 12 Pro Max")
.previewDisplayName("iPhone 12 Pro Max")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment