ProgressBar iOS 13 - swift

I've tried creating my own ProgressView to support iOS 13, but for some reason it appears to not work. I've tried #State, #Binding and the plain var progress: Progress, but it doesn't update at all.
struct ProgressBar: View {
#Binding var progress: Progress
var body: some View {
VStack(alignment: .leading) {
Text("\(Int(progress.fractionCompleted))% completed")
ZStack {
RoundedRectangle(cornerRadius: 2)
.foregroundColor(Color(UIColor.systemGray5))
.frame(height: 4)
GeometryReader { metrics in
RoundedRectangle(cornerRadius: 2)
.foregroundColor(.blue)
.frame(width: metrics.size.width * CGFloat(progress.fractionCompleted))
}
}.frame(height: 4)
Text("\(progress.completedUnitCount) of \(progress.totalUnitCount)")
.font(.footnote)
.foregroundColor(.gray)
}
}
}
In my content view I added both the iOS 14 variant and the iOS 13 supporting one. They look the same, but the iOS 13 variant does not change anything.
struct ContentView: View {
#State private var progress = Progress(totalUnitCount: 10)
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
ProgressBar(progress: $progress)
.padding()
.onReceive(timer) { timer in
progress.completedUnitCount += 1
if progress.isFinished {
self.timer.upstream.connect().cancel()
progress.totalUnitCount = 500
}
}
if #available(iOS 14, *) {
ProgressView(progress)
.padding()
.onReceive(timer) { timer in
progress.completedUnitCount += 1
if progress.isFinished {
self.timer.upstream.connect().cancel()
progress.totalUnitCount = 500
}
}
}
}
}
}
The iOS 14 variant works, but my iOS 13 implementation fails. Can somebody help me?

Progress is-a NSObject, it is not a struct, so state does not work for it. You have to use KVO to observe changes in Progress and redirect into SwiftUI view's source of truth.
Here is a simplified demo of possible solution. Tested with Xcode 12.1 / iOS 14.1
class ProgressBarViewModel: ObservableObject {
#Published var fractionCompleted: Double
let progress: Progress
private var observer: NSKeyValueObservation!
init(_ progress: Progress) {
self.progress = progress
self.fractionCompleted = progress.fractionCompleted
observer = progress.observe(\.completedUnitCount) { [weak self] (sender, _) in
self?.fractionCompleted = sender.fractionCompleted
}
}
}
struct ProgressBar: View {
#ObservedObject private var vm: ProgressBarViewModel
init(_ progress: Progress) {
self.vm = ProgressBarViewModel(progress)
}
var body: some View {
VStack(alignment: .leading) {
Text("\(Int(vm.fractionCompleted * 100))% completed")
ZStack {
RoundedRectangle(cornerRadius: 2)
.foregroundColor(Color(UIColor.systemGray5))
.frame(height: 4)
GeometryReader { metrics in
RoundedRectangle(cornerRadius: 2)
.foregroundColor(.blue)
.frame(width: metrics.size.width * CGFloat(vm.fractionCompleted))
}
}.frame(height: 4)
Text("\(vm.progress.completedUnitCount) of \(vm.progress.totalUnitCount)")
.font(.footnote)
.foregroundColor(.gray)
}
}
}
and updated call place to use same constructor
struct ContentView: View {
#State private var progress = Progress(totalUnitCount: 10)
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
ProgressBar(progress) // << here !!
// ... other code

Related

SwiftUI Popup window

I created my first simple card game.
https://codewithchris.com/first-swiftui-app-tutorial/
Now I want to add a pop up window which will pop up with the message "You win" if the player gets 15 points and "You lose" if the CPU gets 15 points first.
Can someone please help me how to do it?
I would be glad if there is some tutorial so I can do it myself, not just copy and paste it.
import SwiftUI
struct ContentView: View {
#State private var playCard = "card5"
#State private var cpuCard = "card9"
#State private var playerScore = 0
#State private var cpuScore = 0
var body: some View {
ZStack {
Image("background-plain")
.resizable()
.ignoresSafeArea()
VStack{
Spacer()
Image("logo")
HStack{
Spacer()
Image(playCard)
Spacer()
Image(cpuCard)
Spacer()
}
Button(action: {
//reset
playerScore = 0
cpuScore = 0
}, label: {
Image(systemName: "clock.arrow.circlepath")
.font(.system(size: 60))
.foregroundColor(Color(.systemRed)) })
Button(action: {
//gen. random betw. 2 and 14
let playerRand = Int.random(in: 2...14)
let cpuRand = Int.random(in: 2...14)
//Update the cards
playCard = "card" + String(playerRand)
cpuCard = "card" + String(cpuRand)
//Update the score
if playerRand > cpuRand {
playerScore += 1
}
else if cpuRand > playerRand {
cpuScore += 1
}
}, label: {
Image("button")
})
HStack{
Spacer()
VStack{
Text("Player")
.font(.headline)
.padding(.bottom, 10.0)
Text(String(playerScore))
.font(.largeTitle)
}
Spacer()
VStack{
Text("CPU")
.font(.headline)
.padding(.bottom, 10.0)
Text(String(cpuScore))
.font(.largeTitle)
}
Spacer()
}
.foregroundColor(.white)
Spacer()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Make your popup like a view. And after that. Call it in method present a view.
struct ContentView : View {
#State var showingPopup = false // 1
var body: some View {
ZStack {
Color.red.opacity(0.2)
Button("Push me") {
showingPopup = true // 2
}
}
.popup(isPresented: $showingPopup) { // 3
ZStack { // 4
Color.blue.frame(width: 200, height: 100)
Text("Popup!")
}
}
}
}
extension View {
public func popup<PopupContent: View>(
isPresented: Binding<Bool>,
view: #escaping () -> PopupContent) -> some View {
self.modifier(
Popup(
isPresented: isPresented,
view: view)
)
}
}
public struct Popup<PopupContent>: ViewModifier where PopupContent: View {
init(isPresented: Binding<Bool>,
view: #escaping () -> PopupContent) {
self._isPresented = isPresented
self.view = view
}
/// Controls if the sheet should be presented or not
#Binding var isPresented: Bool
/// The content to present
var view: () -> PopupContent
}

Is there a way to get my Apple Watch App to vibrate when the screen sleeps?

I own a Apple Watch series 3 and I made an app for it that vibrates every specified amount of seconds. I know haptics drain your battery but, regardless, its the purpose of my app. I tried to test it on my watch and I encountered a problem. When the screen is on the app functions like its supposed to but as soon as I put my wrist down and the screen sleeps, the app runs in the background but it doesn't vibrate like supposed to. Is there a way around this? Like I said I have a series 3 therefore I don't have access to the always on screen display function.
My app code is below:
import SwiftUI
struct ContentView: View {
#State var timerScreenShown = false
#State var timeVal = 10
var body: some View {
VStack{
Text("Select \(timeVal)s intervals").padding()
Picker(
selection: $timeVal,
label: Text("")){
ForEach(10...120, id: \.self) {
Text("\($0)")
}
}
NavigationLink(destination: SecondView(timerScreenShown: $timerScreenShown, timeVal: timeVal), isActive: $timerScreenShown, label: {Text("Go")})
}
}
}
struct SecondView: View{
#Binding var timerScreenShown:Bool
#State var timeVal = 10
#State var startVal = 0
var body: some View {
VStack{
if timeVal > 0 {
Text("Timer")
.font(.system(size: 14))
Text("\(startVal)")
.font(.system(size: 40))
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if self.timeVal > 0{
self.startVal += 1
if self.timeVal == self.startVal{
WKInterfaceDevice.current().play(.failure)
self.startVal = 0
}
}
}
}
Text("seconds")
.font(.system(size: 14))
Button(action: {
self.timerScreenShown = false
}) {
Text("Cancel")
.foregroundColor(.red)
}
} else {
Button(action: {
self.timerScreenShown = false
}) {
Text("Done")
.foregroundColor(.green)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
ContentView()
}
}
}

Cannot get Haptic Feedback to work with Swift

I'm a CS major in college who had an idea for an Apple Watch app that vibrates every time it reaches a certain time interval, for example the user picks intervals of 20s through a picker, taps "start" and the app starts a timer from 0 to 20 and once it reaches 20 I want the watch to vibrate and start counting up to 20 again until the user chooses to stop it. I want to use it for runners to have an idea of consistent paces to run. The problem is that I can't get UIImpactFeedbackGenerator to work and none of the other methods of haptic feedback either. I have no swift experience, only python and Java but I am basically done except for this haptic part. The one error I get is UIImpactFeedbackGenerator is out of scope where the generator is declared under SecondView. Thank you in advance! Here's my code:
import SwiftUI
struct ContentView: View {
#State var timerScreenShown = false
#State var timeVal = 10
var body: some View {
VStack{
Text("Select \(timeVal)s intervals").padding()
Picker(
selection: $timeVal,
label: Text("")){
ForEach(10...120, id: \.self) {
Text("\($0)")
}
}
NavigationLink(destination: SecondView(timerScreenShown: $timerScreenShown, timeVal: timeVal), isActive: $timerScreenShown, label: {Text("Go")})
}
}
}
struct SecondView: View{
#Binding var timerScreenShown:Bool
#State var timeVal = 10
#State var startVal = 0
var body: some View {
VStack{
if timeVal > 0 {
Text("Timer")
.font(.system(size: 14))
Text("\(startVal)")
.font(.system(size: 40))
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if self.timeVal > 0{
self.startVal += 1
if self.timeVal == self.startVal{
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
self.startVal = 0
}
}
}
}
Text("seconds")
.font(.system(size: 14))
Button(action: {
self.timerScreenShown = false
}) {
Text("Cancel")
.foregroundColor(.red)
}
} else {
Button(action: {
self.timerScreenShown = false
}) {
Text("Done")
.foregroundColor(.green)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
ContentView()
}
}
}
WatchOs does have the play(_:) - method for notification.
try WKInterfaceDevice.current().play(.click)
for more information take a look at the documentation -- https://developer.apple.com/documentation/watchkit/wkinterfacedevice/1628128-play

is it possible get List array to load horizontally in swiftUI?

Do I need to dump using List and just load content into a Scrollview/HStack or is there a horizontal equivalent to stack? I would like to avoid having to set it up differently, but am willing todo so if there is no alternative... it just means recoding multiple other views.
current code for perspective:
import SwiftUI
import Combine
struct VideoList: View {
#Environment(\.presentationMode) private var presentationMode
#ObservedObject private(set) var viewModel: ViewModel
#State private var isRefreshing = false
var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("Home") // set image here
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
}
}
}
var body: some View {
NavigationView {
List(viewModel.videos.sorted { $0.id > $1.id}, id: \.id) { video in
NavigationLink(
destination: VideoDetails(viewModel: VideoDetails.ViewModel(video: video))) {
VideoRow(video: video)
}
}
.onPullToRefresh(isRefreshing: $isRefreshing, perform: {
self.viewModel.fetchVideos()
})
.onReceive(viewModel.$videos, perform: { _ in
self.isRefreshing = false
})
}
.onAppear(perform: viewModel.fetchVideos)
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
In general, List is List and it by design is vertical-only. For all horizontal case we should use ScrollView+HStack or ScrollView+LazyHStack (SwiftUI 2.0).
Anyway here is a simple demo of possible way that can be applicable in some particular cases. Prepared & tested with Xcode 12 / iOS 14.
Note: all tuning and alignments fixes are out of scope - only possibility demo.
struct TestHorizontalList: View {
let data = Array(1...20)
var body: some View {
GeometryReader { gp in
List {
ForEach(data, id: \.self) {
RowDataView(item: $0)
.rotationEffect(.init(degrees: 90)) // << rotate content back
}
}
.frame(height: gp.size.width) // initial fit in screen
.rotationEffect(.init(degrees: -90)) // << rotate List
}
}
}
struct RowDataView: View {
let item: Int
var body: some View {
RoundedRectangle(cornerRadius: 25.0).fill(Color.blue)
.frame(width: 80, height: 80)
.overlay(
Text("\(item)")
)
}
}

Rectangle progress bar swiftUI

Hey does someone know how can I create rectangle progress bar in swiftUI?
Something like this?
https://i.stack.imgur.com/CMwB3.gif
I have tried this:
struct ProgressBar: View
{
#State var degress = 0.0
#Binding var shouldLoad: Bool
var body: some View
{
RoundedRectangle(cornerRadius: cornerRadiusValue)
.trim(from: 0.0, to: CGFloat(degress))
.stroke(Color.Scheme.main, lineWidth: 2.0)
.frame(width: 300, height: 40, alignment: .center)
.onAppear(perform: shouldLoad == true ? {self.start()} : {})
}
func start()
{
Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true)
{
timer in
withAnimation
{
self.degress += 0.3
}
}
}
}
Here is simple demo of possible approach for [0..1] range progress indicator.
Updated: re-tested with Xcode 13.3 / iOS 15.4
struct ProgressBar: View {
#Binding var progress: CGFloat // [0..1]
var body: some View {
RoundedRectangle(cornerRadius: 10)
.trim(from: 0.0, to: CGFloat(progress))
.stroke(Color.red, lineWidth: 2.0)
.animation(.linear, value: progress)
}
}
struct DemoAnimatingProgress: View {
#State private var progress = CGFloat.zero
var body: some View {
Button("Demo") {
if self.progress == .zero {
self.simulateLoading()
} else {
self.progress = 0
}
}
.padding()
.background(ProgressBar(progress: $progress))
}
func simulateLoading() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.progress += 0.1
if self.progress < 1.0 {
self.simulateLoading()
}
}
}
}
Available for XCode 12.
import SwiftUI
//MARK: - ProgressBar
struct ContentView: View {
#State private var downloaded = 0.0
var body: some View {
ProgressView("Downloaded...", value: downloaded, total: 100)
}
}
//MARK: - Circular ProgressBar
struct ContentView: View {
#State private var downloaded = 0.0
var body: some View {
ProgressView("Downloaded...", value: downloaded, total: 100)
.progressViewStyle(CircularProgressViewStyle())
}
}