Skip to content

Instantly share code, notes, and snippets.

@swiftui-lab
Last active April 8, 2022 00:23
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save swiftui-lab/ddbb145fb7397aa8923a4604e91f9b3f to your computer and use it in GitHub Desktop.
Save swiftui-lab/ddbb145fb7397aa8923a4604e91f9b3f to your computer and use it in GitHub Desktop.
// Safely Modifying The View State (SwiftUI)
// https://swiftui-lab.com
// https://swiftui-lab.com/state-changes
import SwiftUI
struct CustomView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: ExampleView1(), label: { Text("Example #1 - OutOfControlView") })
NavigationLink(destination: ExampleView2(), label: { Text("Example #2 - Cardinal Direction") })
NavigationLink(destination: ExampleView3(), label: { Text("Example #3 - Observable Object") })
}
}
}
}
// MARK: - Example #1
struct ExampleView1: View {
@State private var showOutOfControlView = false
var body: some View {
VStack(spacing: 10) {
CPUWheel().frame(height: 150)
VStack {
if showOutOfControlView { OutOfControlView() }
}.frame(height: 80)
Button(self.showOutOfControlView ? "Hide": "Show") {
self.showOutOfControlView.toggle()
}
}
}
}
struct OutOfControlView: View {
@State private var counter = 0
var body: some View {
DispatchQueue.main.async {
self.counter += 1
}
return Text("Computed Times\n\(counter)").multilineTextAlignment(.center)
}
}
// MARK: - Example #2
struct ExampleView2: View {
@State private var flag = false
@State private var cardinalDirection = ""
var body: some View {
return VStack(spacing: 30) {
CPUWheel().frame(height: 150)
Text("\(cardinalDirection)").font(.largeTitle)
Image(systemName: "location.north")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.modifier(RotateNeedle(cardinalDirection: self.$cardinalDirection, angle: self.flag ? 0 : 360))
Button("Animate") {
withAnimation(.easeInOut(duration: 3.0)) {
self.flag.toggle()
}
}
}
}
}
struct RotateNeedle: GeometryEffect {
@Binding var cardinalDirection: String
var angle: Double
var animatableData: Double {
get { angle }
set { angle = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
DispatchQueue.main.async {
self.cardinalDirection = self.angleToString(self.angle)
}
let rotation = CGAffineTransform(rotationAngle: CGFloat(angle * (Double.pi / 180.0)))
let offset1 = CGAffineTransform(translationX: size.width/2.0, y: size.height/2.0)
let offset2 = CGAffineTransform(translationX: -size.width/2.0, y: -size.height/2.0)
return ProjectionTransform(offset2.concatenating(rotation).concatenating(offset1))
}
func angleToString(_ a: Double) -> String {
switch a {
case 315..<405:
fallthrough
case 0..<45:
return "North"
case 45..<135:
return "East"
case 135..<225:
return "South"
default:
return "West"
}
}
}
// MARK: - Example #3
class MyObservable: ObservableObject {
@Published var someValue = "Here's a value!"
}
struct ExampleView3: View {
var body: some View {
VStack(spacing: 20) {
MyView()
MyView()
.environmentObject(MyObservable())
}
}
}
extension EnvironmentObject {
var safeToUse: Bool {
return (Mirror(reflecting: self).children.first(where: { $0.label == "_store" })?.value as? ObjectType) != nil
}
}
struct MyView: View {
@EnvironmentObject var model: MyObservable
var body: some View {
let txt = _model.safeToUse ? model.someValue : "No Value"
return Text(txt)
}
}
// MARK: - CPU Wheel
struct CPUWheel: View {
@State private var cpu: Int = 0
var timer = Timer.publish(every: 0.1, on: .current, in: .common).autoconnect()
var body: some View {
let gradient = AngularGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.red]),
center: .center, angle: Angle(degrees: 0))
return Circle()
.stroke(lineWidth: 3)
.foregroundColor(.primary)
.background(Circle().fill(gradient).clipShape(CPUClip(pct: Double(self.cpu))))
.shadow(radius: 4)
.overlay(CPULabel(pct: self.cpu))
.onReceive(timer) { _ in
withAnimation {
self.cpu = Int(CPUWheel.cpuUsage())
}
}
}
struct CPULabel: View {
let pct: Int
var body: some View {
VStack {
Text("\(self.pct) %")
.font(.largeTitle)
Text("CPU")
.font(.body)
}.transaction({ $0.animation = nil })
}
}
struct CPUClip: Shape {
var pct: Double
var animatableData: Double {
get { pct }
set { pct = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
let c = CGPoint(x: rect.midX, y: rect.midY)
path.move(to: c)
path.addArc(center: c,
radius: rect.width/2.0,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: (pct/100.0) * 360), clockwise: false)
path.closeSubpath()
return path
}
}
// Source for cpuUsage(): based on https://stackoverflow.com/a/44134397/7786555
static func cpuUsage() -> Double {
var kr: kern_return_t
var task_info_count: mach_msg_type_number_t
task_info_count = mach_msg_type_number_t(TASK_INFO_MAX)
var tinfo = [integer_t](repeating: 0, count: Int(task_info_count))
kr = task_info(mach_task_self_, task_flavor_t(TASK_BASIC_INFO), &tinfo, &task_info_count)
if kr != KERN_SUCCESS {
return -1
}
return [thread_act_t]().withUnsafeBufferPointer { bufferPointer in
var thread_list: thread_act_array_t? = UnsafeMutablePointer(mutating: bufferPointer.baseAddress)
var thread_count: mach_msg_type_number_t = 0
defer {
if let thread_list = thread_list {
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: thread_list), vm_size_t(Int(thread_count) * MemoryLayout<thread_t>.stride) )
}
}
kr = task_threads(mach_task_self_, &thread_list, &thread_count)
if kr != KERN_SUCCESS {
return -1
}
var tot_cpu: Double = 0
if let thread_list = thread_list {
for j in 0 ..< Int(thread_count) {
var thread_info_count = mach_msg_type_number_t(THREAD_INFO_MAX)
var thinfo = [integer_t](repeating: 0, count: Int(thread_info_count))
kr = thread_info(thread_list[j], thread_flavor_t(THREAD_BASIC_INFO),
&thinfo, &thread_info_count)
if kr != KERN_SUCCESS {
return -1
}
let threadBasicInfo = convertThreadInfoToThreadBasicInfo(thinfo)
if threadBasicInfo.flags != TH_FLAGS_IDLE {
tot_cpu += (Double(threadBasicInfo.cpu_usage) / Double(TH_USAGE_SCALE)) * 100.0
}
} // for each thread
}
return tot_cpu
}
}
static func convertThreadInfoToThreadBasicInfo(_ threadInfo: [integer_t]) -> thread_basic_info {
var result = thread_basic_info()
result.user_time = time_value_t(seconds: threadInfo[0], microseconds: threadInfo[1])
result.system_time = time_value_t(seconds: threadInfo[2], microseconds: threadInfo[3])
result.cpu_usage = threadInfo[4]
result.policy = threadInfo[5]
result.run_state = threadInfo[6]
result.flags = threadInfo[7]
result.suspend_count = threadInfo[8]
result.sleep_time = threadInfo[9]
return result
}
}
@dndydon
Copy link

dndydon commented Nov 22, 2019

I always learn a lot from your examples. Thanks for sharing!

@afern247
Copy link

Many thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment