Skip to content

Instantly share code, notes, and snippets.

@CodeSlicing
Created July 28, 2021 22:49
Show Gist options
  • Save CodeSlicing/b336411878f88aa04b17108f42bd453d to your computer and use it in GitHub Desktop.
Save CodeSlicing/b336411878f88aa04b17108f42bd453d to your computer and use it in GitHub Desktop.
AppleTvRemote source code for episode on multiple cursors in Xcode
//
// AppleTVRemote.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.
//
// Created by Adam Fordyce on 30/07/2021.
// Copyright © 2020 Adam Fordyce. All rights reserved.
//
import SwiftUI
import PureSwiftUI
private let silverGradient = LinearGradient([.white(0.85), .white(0.65)], to: .bottomTrailing)
private let outerDialGradient = LinearGradient([.white(0.18), .white(0.02)], to: .bottomTrailing)
private let buttonBackgroundColor = Color.white(0.05)
private let buttonForegroundColor = Color.white(0.65)
struct AppleTVRemote: View {
var body: some View {
let siriButtonGradient = LinearGradient([(.white(0.9), 0), (.white(0.65), 0.15), (.white(0.65), 0.85), (.white(0.4), 1)], to: .bottom)
GeometryReader { (geo: GeometryProxy) in
let controlSize = getControlSize(from: geo)
ZStack {
Color.black.mask(ControlBackground(controlSize: controlSize))
.offset(.square(controlSize.widthScaled(0.03)))
.blur(controlSize.widthScaled(0.03))
Rectangle()
.fill(siriButtonGradient)
.frame(controlSize.scaled(0.03, 0.1))
.offset(controlSize.scaled(0.5, -0.296))
ControlBackground(controlSize: controlSize)
VStack(spacing: 0) {
MicrophoneAndPowerButton(controlSize: controlSize)
Group {
MainDial(controlSize: controlSize)
.padding(.bottom, controlSize.heightScaled(0.01))
.shadow(controlSize.widthScaled(0.01), offset: .point(controlSize.widthScaled(0.005)))
ButtonCluster(controlSize: controlSize)
.shadow(controlSize.widthScaled(0.015), offset: .point(controlSize.widthScaled(0.007)))
Spacer()
}
.foregroundColor(buttonForegroundColor)
.fontSize(controlSize.widthScaled(0.1))
}
}
.frame(controlSize)
.greedyFrame()
}
}
private func getControlSize(from geo: GeometryProxy) -> CGSize {
let widthAsFractionOfHeight: CGFloat = 0.25
if geo.width > geo.heightScaled(widthAsFractionOfHeight) {
return CGSize(geo.heightScaled(widthAsFractionOfHeight), geo.height)
} else {
return CGSize(geo.width, geo.widthScaled(1 / widthAsFractionOfHeight))
}
}
}
private struct ControlBackground: View {
let controlSize: CGSize
var body: some View {
RoundedRectangle(controlSize.widthScaled(0.18))
.fill(silverGradient)
}
}
private struct MicrophoneAndPowerButton: View {
let controlSize: CGSize
var body: some View {
let silverGradient = LinearGradient([.white(0.85), .white(0.55)], to: .bottomTrailing)
ZStack {
Capsule()
.frame(controlSize.scaled(0.065, 0.005))
HStack {
Spacer()
Circle()
.stroke(silverGradient, lineWidth: controlSize.widthScaled(0.004))
.overlay(SFSymbol(.power).resizedToFit(controlSize.widthScaled(0.085)))
.frame(controlSize.widthScaled(0.19))
}
}
.padding([.top, .horizontal], controlSize.widthScaled(0.1))
.padding(.bottom, controlSize.widthScaled(0.07))
}
}
private struct MainDial: View {
let controlSize: CGSize
var body: some View {
let innerDialGradient = RadialGradient([.white(0.14), .white(0.03)], center: UnitPoint(0.8, 0.8), from: controlSize.widthScaled(0.1), to: controlSize.widthScaled(0.6))
let ringBlurRadius = max(controlSize.widthScaled(0.004), 0.6)
let ringShadowOffset = max(controlSize.widthScaled(0.004), 1)
ZStack {
Color.black
Group {
Circle()
.fill(outerDialGradient)
Circle()
.fill(outerDialGradient)
.rotate(180.degrees)
.frame(controlSize.widthScaled(0.73))
}
.blur(controlSize.widthScaled(0.04))
Group {
innerDialGradient
.clipCircle()
Circle()
.stroke(LinearGradient([.white(0.3), .white(0.5)], to: .bottomTrailing), lineWidth: controlSize.widthScaled(0.005))
.offset(.square(ringShadowOffset))
.blur(ringBlurRadius)
.blendMode(.screen)
Circle()
.stroke(Color.white(0.03), lineWidth: controlSize.widthScaled(0.01))
}
.frame(controlSize.widthScaled(0.53))
ForEach(0..<4) { index in
Circle()
.fillColor(buttonForegroundColor)
.frame(controlSize.widthScaled(0.018))
.xOffset(controlSize.widthScaled(0.4))
.rotate(90.degrees * index.asDouble)
}
}
.clipCircle()
.frame(controlSize.widthScaled(0.9))
}
}
private struct ButtonCluster: View {
let controlSize: CGSize
var body: some View {
let buttonDiameter = controlSize.widthScaled(0.35)
let concaveButtonGradient = RadialGradient([.white(0.14), .white(0.03)], center: UnitPoint(0.8, 0.8), from: controlSize.widthScaled(0.05), to: controlSize.widthScaled(0.28))
VStack(spacing: controlSize.heightScaled(0.02)) {
HStack(spacing: controlSize.widthScaled(0.08)) {
ZStack {
concaveButtonGradient
.frame(buttonDiameter)
.clipCircle()
SFSymbol(.chevron_left)
}
SingleButton(diameter: buttonDiameter, sfSymbol: .tv)
}
HStack(spacing: controlSize.widthScaled(0.08)) {
VStack(spacing: controlSize.heightScaled(0.02)) {
SingleButton(diameter: buttonDiameter, sfSymbol: .playpause)
SingleButton(diameter: buttonDiameter, sfSymbol: .speaker_slash)
}
Capsule()
.fill(buttonBackgroundColor)
.frame(controlSize.scaled(0.35, 0.195))
.overlay(
VStack {
SFSymbol(.plus)
.vPadding(controlSize.heightScaled(0.03))
Spacer()
SFSymbol(.minus)
.vPadding(controlSize.heightScaled(0.04))
}
)
}
}
}
}
private struct SingleButton: View {
let diameter: CGFloat
let sfSymbol: SFSymbolName
var body: some View {
Circle()
.fill(Color.white(0.05))
.overlay(SFSymbol(sfSymbol))
.frame(diameter)
}
}
struct AppleTVRemote_Previews: PreviewProvider {
struct AppleTVRemote_Harness: View {
var body: some View {
HStack(alignment: .top, spacing: 100) {
AppleTVRemote()
.frame(200, 800)
}
.vPadding(80)
.hPadding(120)
.backgroundColor(.white(0.1))
}
}
static var previews: some View {
AppleTVRemote_Harness()
.previewSizeThatFits()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment