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

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()
}
}
}

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
}

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

Getting Values from own PickerView

I'm new to Swift and I'm currently developing my own Timer Application for practice purposes. (I do it without storyboard)
Now I have the Problem that i made a View called "TimePickerView" (Code below), where I created my own Picker. Then I use that TimePickerView in another part of my Application with other Views (in a View). In that View I want to pick my time but I don't know how i can get the Values of the Picker (The Picker works by the way)
This is my TimePickerView
import SwiftUI
struct TimePickerView: View {
#State private var selectedTimeIndexSecond = 0
#State private var selectedTimeIndexMinutes = 0
#State private var seconds : [Int] = Array(0...59)
#State private var minutes : [Int] = Array(0...59)
var body: some View {
VStack{
Text("Select Your Time")
HStack{
//minutes-Picker
Picker("select time", selection: $selectedTimeIndexMinutes, content: {
ForEach(0..<minutes.count, content: {
index in
Text("\(minutes[index]) min").tag(index)
})
})
.padding()
.frame(width: 120)
.clipped()
//seconds-Picker
Picker("select time", selection: $selectedTimeIndexSecond, content: {
ForEach(0..<seconds.count, content: {
index in
Text("\(seconds[index]) sec").tag(index)
})
})
.padding()
.frame(width: 120)
.clipped()
Spacer()
}
Text("You picked the time")
.multilineTextAlignment(.center)
.font(.title2)
.padding()
Text("\(minutes[selectedTimeIndexMinutes]) min : \(seconds[selectedTimeIndexSecond]) sec")
.font(.title)
.bold()
.padding(.top, -14.0)
}
}
func getValues() -> (Int, Int) {
return (self.minutes[selectedTimeIndexMinutes] ,self.seconds[selectedTimeIndexSecond])
}
}
and that is the View I want to use my Picker, but I don't know how I get those values from the Picker:
struct SetTimerView: View {
#State var timepicker = TimePickerView()
var body: some View {
NavigationView{
VStack{
//Select the time
timepicker
//Timer variables (This doesn't work)
var timerTime = timepicker.getValues()
var minutes = timerTime.0
var seconds = timerTime.1
Spacer()
let valid : Bool = isValid(timerTime: minutes+seconds)
//Confirm the time
NavigationLink(
destination:
getRightView(
validBool: valid,
timerTime: minutes*60 + seconds),
label: {
ConfirmButtonView(buttonText: "Confirm")
});
Spacer()
}
}
}
func isValid(timerTime : Int) -> Bool {
if (timerTime == 0) {
return false
} else {
return true
}
}
#ViewBuilder func getRightView(validBool : Bool, timerTime : Int) -> some View{
if (validBool == true) {
TimerView(userTime: CGFloat(timerTime), name: "David", isActive: true)
} else {
UnvalidTimeView()
}
}
}
I think main problem is misunderstanding conceptions between data and views.
At first you need a model witch will override your data (create it in separate swift file):
import Foundation
class Time: ObservableObject {
#Published var selectedTimeIndexMinutes: Int = 0
#Published var selectedTimeIndexSecond: Int = 0
}
Pay attention on ObservableObject so that swiftUI can easily detect changes to it that trigger any active views to redraw.
Next I try to change the value of the model in the view
import SwiftUI
struct TimePickerView: View {
#EnvironmentObject var timeData: Time
#State private var seconds : [Int] = Array(0...59)
#State private var minutes : [Int] = Array(0...59)
var body: some View {
VStack{
Text("Select Your Time")
HStack{
//minutes-Picker
Picker("select time", selection: $timeData.selectedTimeIndexMinutes, content: {
ForEach(0..<minutes.count, content: {
index in
Text("\(minutes[index]) min").tag(index)
})
})
.padding()
.frame(width: 120)
.clipped()
//seconds-Picker
Picker("select time", selection: $timeData.selectedTimeIndexSecond, content: {
ForEach(0..<seconds.count, content: {
index in
Text("\(seconds[index]) sec").tag(index)
})
})
.padding()
.frame(width: 120)
.clipped()
Spacer()
}
Text("You picked the time")
.multilineTextAlignment(.center)
.font(.title2)
.padding()
Text("\(timeData.selectedTimeIndexMinutes) min : \(timeData.selectedTimeIndexSecond) sec")
.font(.title)
.bold()
.padding(.top, -14.0)
}
}
}
struct TimePickerView_Previews: PreviewProvider {
static var previews: some View {
TimePickerView()
.environmentObject(Time())
}
}
Like you can see I don't using #Blinding, instead of it I connecting our Model with a View
On the next view I can see changes, I created a new one because your example have view that don't indicated here...
import SwiftUI
struct ReuseDataFromPicker: View {
#EnvironmentObject var timeData: Time
var body: some View {
VStack{
Text("You selected")
Text("\(timeData.selectedTimeIndexMinutes) min and \(timeData.selectedTimeIndexSecond) sec")
}
}
}
struct ReuseDataFromPicker_Previews: PreviewProvider {
static var previews: some View {
ReuseDataFromPicker()
.environmentObject(Time())
}
}
And collect all in a Content View
struct ContentView: View {
var body: some View {
TabView {
TimePickerView()
.tabItem {Label("Set Timer", systemImage: "clock.arrow.2.circlepath")}
ReuseDataFromPicker()
.tabItem {Label("Show Timer", systemImage: "hourglass")}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Time())
}
}
Like that you can easily change or reuse your data on any other views

Embedded navigationlinks

I am trying to develop an app to do a little cognitive testing
When opening the app there is a NavigationView{} with NavigationLink{}
So far so normal
One of the links
NavigationLink(destination: IntermediateCoreView()) { Text("Go to tests") }
Takes you to a 'prescreen for the tests, from which you can link to the 'test' its self
From the IntermediateCoreView.swift you can NavigationLink(destination: ContentView()) { Text("+ new test") }
Which works, you can take the test. At the end of the test (X seconds pass) and then it displays an alert that the user has run out of time and takes them back to the Intermediate CoreView
This is where it goes wrong as the IntermediateCoreView, 'Back' button in the NavigationView goes back to the 'test' view. Not back to the InitialView
I get that this is 'expected behaviour', the test is back from the screen its been sent to. Is there a way to override this?
To add a minimal example - the .app file:
import SwiftUI
#main
struct minRep2App: App {
var body: some Scene {
WindowGroup {
initialView()
}
}
}
Then the initial view controller:
import SwiftUI
struct initialView: View {
var body: some View {
NavigationView{
List{
NavigationLink(destination: ContentView()) {
Text("Go to CoRe tests")
}
}
}
}
}
struct initialView_Previews: PreviewProvider {
static var previews: some View {
initialView()
}
}
Lastly the test demo
import SwiftUI
struct ContentView: View {
#State private var timeRemaining = 50.00
#State private var showingAlert = false
#State var showIntermediate: Bool = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
NavigationLink(destination: initialView(), isActive: self.$showIntermediate)
{
EmptyView()
}
Text("Test goes here")
.padding()
HStack{
// Text("Score: \(score)")
Text("Time: \(timeRemaining)")
}.padding(.bottom, 10)
.onReceive(timer) { time in
if self.timeRemaining > 0.1 {
self.timeRemaining -= 1
}
if self.timeRemaining == 0.0 {
self.showingAlert = true
self.timer.upstream.connect().cancel()
}
}
.alert(isPresented: $showingAlert){
Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
self.showIntermediate = true
})
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Change your ContentView to this:
import SwiftUI
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State private var timeRemaining = 50.00
#State private var showingAlert = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
Text("Test goes here")
.padding()
HStack{
// Text("Score: \(score)")
Text("Time: \(timeRemaining)")
}.padding(.bottom, 10)
.onReceive(timer) { time in
if self.timeRemaining > 0.1 {
self.timeRemaining -= 1
}
if self.timeRemaining == 0.0 {
self.showingAlert = true
self.timer.upstream.connect().cancel()
}
}
.alert(isPresented: $showingAlert){
Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
self.presentationMode.wrappedValue.dismiss()
})
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This has nothing to do with the back button which will work fine. The problem is the logic in your alert caused you to instantiate a new, wholly different, InitialView. Think of it like webpages. If you navigate from one to the next, and then click on a link that goes to the first one, you have actually gone FORWARD to the first page, and if you click the back button you will get to the second page. Same thing here.
Instead, you should use #Environment(\.presentationMode) var presentationMode which, when you call self.presentationMode.wrappedValue.dismiss() will trigger you to go back to the view that presented the view that you are trying to navigate back from.

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)")
)
}
}