Apple has introduced new LockScreen widgets, in which, as an example, they have made a progress widget with a point around which there is a void
How can I make such a void effect on SwiftUI ?
You need to use SwiftUI's Gauge with .gaugeStyle(.accessoryCircular). Here's an example from Apple's documentation:
struct LabeledGauge: View {
#State private var current = 67.0
#State private var minValue = 0.0
#State private var maxValue = 170.0
var body: some View {
Gauge(value: current, in: minValue...maxValue) {
Text("BPM")
} currentValueLabel: {
Text("\(Int(current))")
} minimumValueLabel: {
Text("\(Int(minValue))")
} maximumValueLabel: {
Text("\(Int(maxValue))")
}
.gaugeStyle(.accessoryCircular)
}
}
Related
In my content View, I am initializing the views "ringView" and "ringNumberView" which are shown on screen. However, the data shown by these views is constantly changing and I would like the user to be able to refresh them at any point via the "refresh" button. With the button I am attempting to reset the views with current data by recreating them but they do not changed when "refresh" is clicked.
content View:
struct ContentView: View {
#State var WeekElevation : FlightsClimbed = FlightsClimbed() //ignore this
#State var ringView : RingView = RingView() //creating them here
#State var ringNumberView = RingNumbersView()
var body: some View {
Button("refresh") { //trying to recreate them here
ringView = RingView()
ringNumberView = RingNumbersView()
}
ringView.frame(width: 45, height: 45)
ringNumberView
VStack { // ignore this
Text("\(Int(WeekElevation.getFlights())) ") + Text(Image(systemName: "figure.stairs")) + Text(" in past week")
Text("\(WeekElevation.calculatePercentEverest())% of Mt. Everest")
}
}
}
RingView:
import Foundation
import HealthKit
import SwiftUI
struct RingView : WKInterfaceObjectRepresentable {
#StateObject var fitness = main()
func makeWKInterfaceObject(context: Context) -> some WKInterfaceObject {
let ringObject = WKInterfaceActivityRing()
fitness.makeQuery() { summary in
ringObject.setActivitySummary(summary, animated: true)
}
return ringObject
}
func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceObjectType, context: Context) {
}
}
RingNumbersView:
struct RingNumbersView: View {
#StateObject var fitness = main()
#State var ActivitySummary : HKActivitySummary = HKActivitySummary()
var body: some View {
let red = Int(ActivitySummary.activeEnergyBurned.doubleValue(for: .largeCalorie()))
let green = Int(ActivitySummary.appleExerciseTime.doubleValue(for: .minute()))
let blue = Int(ActivitySummary.appleStandHours.doubleValue(for: .count()))
HStack {
Text("\(red)").foregroundColor(.red)
Text("\(green)").foregroundColor(.green)
Text("\(blue)").foregroundColor(.blue)
}.padding().onAppear(){
fitness.authorizeHealthkit()
fitness.makeQuery() { summary in
ActivitySummary = summary
}
}
}
}
Thanks very much for any help
Every time you call
main()
You create a different instance. One does not know about the other.
Use
#ObservedObject
Or
#EnvironmentObject
For the child Views.
Also, Views should not be in an
#State
Only in a
body
Or
#ViewBuilder
I try to maing a global publisher,like:
#Published var counter = 0
But here is a error:
Property wrappers are not yet supported in top-level code
How to share global Publisher variables between models?
If you want a global variable that publishes a value in a similar way to an #Published property you can do
var counter = CurrentValueSubject<Int,Never>(0)
let subscription = counter.sink { print("The counter is \($0)") }
counter1.value = 3
But if you are using SwiftUI then as Joakim Danielson pointed out in comments you should look into making the value part of the Environment rather than having a global.
That solution would look something like:
private struct EnvironmentCounter: EnvironmentKey {
typealias Value = Int
static var defaultValue: Int = 0
static let farReachingCounter: String = "Far Reaching Counter"
}
extension EnvironmentValues {
var farReachingCounter: Int {
get { self[EnvironmentCounter.self] }
set { self[EnvironmentCounter.self] = newValue }
}
}
struct LeafView : View {
#Environment(\.farReachingCounter) var counter : Int
var body: some View {
Text("The counter is \(counter)")
}
}
struct IntermediateView : View {
var body: some View {
LeafView()
}
}
struct TopLevelView: View {
#State var counter = 0
var body: some View {
VStack {
IntermediateView()
HStack {
Button(action: { counter = counter + 1 }) {
Text("Increment")
}
Button(action: { counter = counter - 1 }) {
Text("Decrement")
}
}
}.environment(\.farReachingCounter, counter)
}
}
PlaygroundSupport.PlaygroundPage.current.liveView = UIHostingController(rootView: TopLevelView())
This code declares an EnvironmentKey that views can use to pull values out of the Environment. In this case the key is farReachingCounter.
Then it declares an extension on EnvironmentValues that gets and sets the value in and gets the value from the environment.
Finally I show a SwiftUI hierarchy where the TopLevelView defines the counter value for its whole hierarchy. There is an IntermediateView that doesn't reference the court counter at all (it's just there to show that the counter passes down the view hierarchy through the environment). Then the LeafView shows how you can pull a custom value out of the environment.
The farReachingCounter is not global to the whole module, but it is part of the SwiftUI environment from the TopLevelView down.
Here is the solution :
class Counter: ObservableObject {
#Published var counter = 0
static var shared = Counter.init()
private init() { //Just to hide the method
}
}
Specific question:
SwiftUI doesn't like us initializing #State using parameters from the parent, but what if the parent holding that #State causes major performance issues?
Example:
How do I make tapping on the top text change the slider to full/empty?
Dragging the slider correctly communicates upwards when the slider changes from full to empty, but tapping the [Overview] full: text doesn't communicate downwards that the slider should change to full/empty.
I could store the underlying Double in the parent view, but it causes major lag and seems unnecessary.
import SwiftUI
// Top level View. It doesn't know anything about specific slider percentages,
// it only knows if the slider got moved to full/empty
struct SliderOverviewView: View {
// Try setting this to true and rerunning.. It DOES work here?!
#State var overview = OverviewModel(state: .empty)
var body: some View {
VStack {
Text("[Overview] full: \(overview.state.rawValue)")
.onTapGesture { // BROKEN: should update child..
switch overview.state {
case .full, .between: overview.state = .empty
case .empty: overview.state = .full
}
}
SliderDetailView(overview: $overview)
}
}
}
// Bottom level View. It knows about specific slider percentages and only
// communicates upwards when percentage goes to 0% or 100%.
struct SliderDetailView: View {
#State var details: DetailModel
init(overview: Binding<OverviewModel>) {
details = DetailModel(overview: overview)
}
var body: some View {
VStack {
Text("[Detail] percentFull: \(details.percentFull)")
Slider(value: $details.percentFull)
.padding(.horizontal, 48)
}
}
}
// Top level model that only knows if slider went to 0% or 100%
struct OverviewModel {
var state: OverviewState
enum OverviewState: String {
case empty
case between
case full
}
}
// Lower level model that knows full slider percentage
struct DetailModel {
#Binding var overview: OverviewModel
var percentFull: Double {
didSet {
if percentFull == 0 {
overview.state = .empty
} else if percentFull == 1 {
overview.state = .full
} else {
overview.state = .between
}
}
}
init(overview: Binding<OverviewModel>) {
_overview = overview
// set inital percent
switch overview.state.wrappedValue {
case .empty:
percentFull = 0.0
case .between:
percentFull = 0.5
case .full:
percentFull = 1.0
}
}
}
struct SliderOverviewView_Previews: PreviewProvider {
static var previews: some View {
SliderOverviewView()
}
}
Why don't I just store percentFull in the OverviewModel?
I'm looking for a pattern so my top level #State struct doesn't need to know EVERY low level detail specific to certain Views.
Running the code example is the clearest way to see my problem.
This question uses a contrived example where an Overview only knows if the slider is full or empty, but the Detail knows what percentFull the slider actually is. The Detail has very detailed control and knowledge of the slider, and only communicates upwards to the Overview when the slider is 0% or 100%
What's my specific case for why I need to do this?
For those curious, my app is running into performance issues because I have several gestures that give the user control over progress. I want my top level ViewModel to store if the gesture is complete or not, but it doesn't need to know the specifics of how far the user has swiped. I'm trying to hide this specific progress Double from my higher level ViewModel to improve app performance.
Here is working, simplified and refactored answer for your issue:
struct ContentView: View {
var body: some View {
SliderOverviewView()
}
}
struct SliderOverviewView: View {
#State private var overview: OverviewModel = OverviewModel(full: false)
var body: some View {
VStack {
Text("[Overview] full: \(overview.full.description)")
.onTapGesture {
overview.full.toggle()
}
SliderDetailView(overview: $overview)
}
}
}
struct SliderDetailView: View {
#Binding var overview: OverviewModel
var body: some View {
VStack {
Text("[Detail] percentFull: \(tellValue(value: overview.full))")
Slider(value: Binding(get: { () -> Double in
return tellValue(value: overview.full)
}, set: { newValue in
if newValue == 1 { overview.full = true }
else if newValue == 0 { overview.full = false }
}))
}
}
func tellValue(value: Bool) -> Double {
if value { return 1 }
else { return 0 }
}
}
struct OverviewModel {
var full: Bool
}
Update:
struct SliderDetailView: View {
#Binding var overview: OverviewModel
#State private var sliderValue: Double = Double()
var body: some View {
VStack {
Text("[Detail] percentFull: \(sliderValue)")
Slider(value: $sliderValue, in: 0.0...1.0)
}
.onAppear(perform: { sliderValue = tellValue(value: overview.full) })
.onChange(of: overview.full, perform: { newValue in
sliderValue = tellValue(value: newValue)
})
.onChange(of: sliderValue, perform: { newValue in
if newValue == 1 { overview.full = true }
else { overview.full = false }
})
}
func tellValue(value: Bool) -> Double {
value ? 1 : 0
}
}
I present here a clean alternative using 2 ObservableObject, a hight level OverviewModel that
only deal with if slider went to 0% or 100%, and a DetailModel that deals only with the slider percentage.
Dragging the slider correctly communicates upwards when the slider changes from full to empty, and
tapping the [Overview] full: text communicates downwards that the slider should change to full/empty.
import Foundation
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#StateObject var overview = OverviewModel()
var body: some View {
SliderOverviewView().environmentObject(overview)
}
}
// Top level View. It doesn't know anything about specific slider percentages,
// it only cares if the slider got moved to full/empty
struct SliderOverviewView: View {
#EnvironmentObject var overview: OverviewModel
var body: some View {
VStack {
Text("[Overview] full: \(overview.state.rawValue)")
.onTapGesture {
switch overview.state {
case .full, .between: overview.state = .empty
case .empty: overview.state = .full
}
}
SliderDetailView()
}
}
}
// Bottom level View. It knows about specific slider percentages and only
// communicates upwards when percentage goes to 0% or 100%.
struct SliderDetailView: View {
#EnvironmentObject var overview: OverviewModel
#StateObject var details = DetailModel()
var body: some View {
VStack {
Text("[Detail] percentFull: \(details.percentFull)")
Slider(value: $details.percentFull).padding(.horizontal, 48)
.onChange(of: details.percentFull) { newVal in
switch newVal {
case 0: overview.state = .empty
case 1: overview.state = .full
default: break
}
}
}
// listen for the high level OverviewModel changes
.onReceive(overview.$state) { theState in
details.percentFull = theState == .full ? 1.0 : 0.0
}
}
}
enum OverviewState: String {
case empty
case between
case full
}
// Top level model that only knows if slider went to 0% or 100%
class OverviewModel: ObservableObject {
#Published var state: OverviewState = .empty
}
// Lower level model that knows full slider percentage
class DetailModel: ObservableObject {
#Published var percentFull = 0.0
}
I know that State wrappers are for View and they designed for this goal, but I wanted to try build and test some code if it is possible, my goal is just for learning purpose,
I have 2 big issues with my code!
Xcode is unable to find T.
How can I initialize my state?
import SwiftUI
var state: State<T> where T: StringProtocol = State(get: { state }, set: { newValue in state = newValue })
struct ContentView: View {
var body: some View {
Text(state)
}
}
Update: I could do samething for Binding here, Now I want do it for State as well with up code
import SwiftUI
var state2: String = String() { didSet { print(state2) } }
var binding: Binding = Binding.init(get: { state2 }, set: { newValue in state2 = newValue })
struct ContentView: View {
var body: some View {
TextField("Enter your text", text: binding)
}
}
If I could find the answer of my issue then, i can define my State and Binding both outside of View, 50% of this work done and it need another 50% for State Wrapper.
New Update:
import SwiftUI
var state: State<String> = State.init(initialValue: "Hello") { didSet { print(state.wrappedValue) } }
var binding: Binding = Binding.init(get: { state.wrappedValue }, set: { newValue in state = State(wrappedValue: newValue) })
struct ContentView: View {
var body: some View {
Text(state) // <<: Here is the issue!
TextField("Enter your text", text: binding)
}
}
Even if you create a State wrapper outside a view, how will the view know when to refresh its body?
Without a way to notify the view, your code will do the same as:
struct ContentView: View {
var body: some View {
Text("Hello")
}
}
What you can do next depends on what you want to achieve.
If all you need is a way to replicate the State behaviour outside the view, I recommend you take a closer look at the Combine framework.
An interesting example is CurrentValueSubject:
var state = CurrentValueSubject<String, Never>("state1")
It stores the current value and also acts as a Publisher.
What will happen if we use it in a view that doesn't observe anything?
struct ContentView: View {
var body: some View {
Text(state.value)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
state.value = "state2"
}
}
}
}
The answer is: nothing. The view is drawn once and, even if the state changes, the view won't be re-drawn.
You need a way to notify the view about the changes. In theory you could do something like:
var state = CurrentValueSubject<String, Never>("state1")
struct ContentView: View {
#State var internalState = ""
var body: some View {
Text(internalState)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
state.value = "state2"
}
}
.onReceive(state) {
internalState = $0
}
}
}
But this is neither elegant nor clean. In these cases we should probably use #State:
struct ContentView: View {
#State var state = "state1"
var body: some View {
Text(state)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
state = "state2"
}
}
}
}
To sum up, if you need a view to be refreshed, just use the native SwiftUI property wrappers (like #State). And if you need to declare state values outside the view, use ObservableObject + #Published.
Otherwise there is a huge Combine framework which does exactly what you want. I recommend you take a look at these links:
Combine: Getting Started
Using Combine
I am quite new to SwiftUI. I have a following "Counter" view that counts up every second. I want to "reset" the counter when the colour is changed:
struct MyCounter : View {
let color: Color
#State private var count = 0
init(color:Color) {
self.color = color
_count = State(initialValue: 0)
}
var body: some View {
Text("\(count)").foregroundColor(color)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}
}
}
Here is my main view that uses counter:
struct ContentView: View {
#State var black = true
var body: some View {
VStack {
MyCounter(color: black ? Color.black : Color.yellow)
Button(action:{self.black.toggle()}) { Text("Toggle") }
}
}
}
When i click "Toggle" button, i see MyCounter constructor being called, but #State counter persists and never resets. So my question is how do I reset this #State value? Please note that I do not wish to use counter as #Binding and manage that in the parent view, but rather MyCounter be a self-contained widget. (this is a simplified example. the real widget I am creating is a sprite animator that performs sprite animations, and when I swap the image, i want the animator to start from frame 0). Thanks!
There are two way you can solve this issue. One is to use a binding, like E.Coms explained, which is the easiest way to solve your problem.
Alternatively, you could try using an ObservableObject as a view model for your timer. This is the more flexible solution. The timer can be passed around and it could also be injected as an environment object if you so desire.
class TimerModel: ObservableObject {
// The #Published property wrapper ensures that objectWillChange signals are automatically emitted.
#Published var count: Int = 0
init() {}
func start() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}
func reset() {
count = 0
}
}
Your timer view then becomes
struct MyCounter : View {
let color: Color
#ObservedObject var timer: TimerModel
init(color: Color, timer: TimerModel) {
self.color = color
self.timer = timer
}
var body: some View {
Text("\(timer.count)").foregroundColor(color)
.onAppear(){
self.timer.start()
}
}
}
Your content view becomes
struct ContentView: View {
#State var black = true
#ObservedObject var timer = TimerModel()
var body: some View {
VStack {
MyCounter(color: black ? Color.black : Color.yellow, timer: self.timer)
Button(action: {
self.black.toggle()
self.timer.reset()
}) {
Text("Toggle")
}
}
}
}
The advantage of using an observable object is that you can then keep track of your timer better. You could add a stop() method to your model, which invalidates the timer and you can call it in a onDisappear block of your view.
One thing that you have to be careful about this approach is that when you're using the timer in a standalone fashion, where you create it in a view builder closure with MyCounter(color: ..., timer: TimerModel()), every time the view is rerendered, the timer model is replaced, so you have to make sure to keep the model around somehow.
You need a binding var:
struct MyCounter : View {
let color: Color
#Binding var count: Int
var body: some View {
Text("\(count)").foregroundColor(color)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}
}
}
struct ContentView: View {
#State var black = true
#State var count : Int = 0
var body: some View {
VStack {
MyCounter(color: black ? Color.black : Color.yellow , count: $count)
Button(action:{self.black.toggle()
self.count = 0
}) { Text("Toggle") }
}
}
}
Also you can just add one State Value innerColor to help you if you don't like binding.
struct MyCounter : View {
let color: Color
#State private var count: Int = 0
#State private var innerColor: Color?
init(color: Color) {
self.color = color
}
var body: some View {
return Text("\(self.count)")
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}.foregroundColor(color).onReceive(Just(color), perform: { color in
if self.innerColor != self.color {
self.count = 0
self.innerColor = color}
})
}
}