I have a demo SwiftUI for selected two color & value as below, I want to collect the color value when user select different color.
But they're always linked same value and I cannot separate the two 'Stepper' and got them into an Array.
import SwiftUI
struct StepColorChange: View {
#State private var value = 0
let colors: [Color] = [.orange, .red, .gray, .blue, .green, .purple, .pink]
func incrementStep() {
value += 1
if value >= colors.count { value = 0}
}
func decrementStep() {
value -= 1
if value < 0 {
value = colors.count - 1}
}
var body: some View {
// change color with value stepper
VStack {
Stepper {
Text("Value: \(value) Color: \(colors[value].description)")
} onIncrement: {
incrementStep()
} onDecrement: {
decrementStep()
}
.padding(5)
.background(colors[value])
Stepper {
Text("Value: \(value) Color: \(colors[value].description)")
} onIncrement: {
incrementStep()
} onDecrement: {
decrementStep()
}
.padding(5)
.background(colors[value])
}
}
}
Thanks a lot!
This is not rocket science here. Try something like this, using two values,
to change the stepper background color independently according each stepper value.
struct ContentView: View {
var body: some View {
StepColorChange()
}
}
struct StepColorChange: View {
#State private var value1 = 0
#State private var value2 = 0
let colors: [Color] = [.orange, .red, .gray, .blue, .green, .purple, .pink]
var body: some View {
VStack {
Stepper {
Text("Value: \(value1) Color: \(colors[value1].description)")
} onIncrement: {
value1 += 1
if value1 >= colors.count { value1 = 0}
} onDecrement: {
value1 -= 1
if value1 < 0 { value1 = colors.count - 1 }
}
.padding(5)
.background(colors[value1])
Stepper {
Text("Value: \(value2) Color: \(colors[value2].description)")
} onIncrement: {
value2 += 1
if value2 >= colors.count { value2 = 0}
} onDecrement: {
value2 -= 1
if value2 < 0 { value2 = colors.count - 1 }
}
.padding(5)
.background(colors[value2])
}
}
}
EDIT-1: condensed code
struct StepColorChange: View {
#State private var values = [0,0]
let colors: [Color] = [.orange, .red, .gray, .blue, .green, .purple, .pink]
func stepper(_ ndx: Int) -> some View {
Stepper {
Text("Value: \(values[ndx]) Color: \(colors[values[ndx]].description)")
} onIncrement: {
values[ndx] += 1
if values[ndx] >= colors.count { values[ndx] = 0}
} onDecrement: {
values[ndx] -= 1
if values[ndx] < 0 { values[ndx] = colors.count - 1}
}
.padding(5)
.background(colors[values[ndx]])
}
var body: some View {
VStack {
stepper(0)
stepper(1)
Text("stepper(0): \(values[0])")
Text("stepper(1): \(values[1])")
Text("color stepper(0): \(colors[values[0]].description)")
Text("color stepper(1): \(colors[values[1]].description)")
}
}
}
Related
How can I make a Form in which the elements are automatically divided into sections based on their first letter and add to the right the alphabet jumper to show the elements starting by the selected letter (just like the Contacts app)?
I also noted a strange thing that I have no idea how to recreate: not all letters are shown, some of them appear as "•". However, when you tap on them, they take you to the corresponding letter anyway. I tried using a ScrollView(.vertical) inside a ZStack and adding .scrollTo(selection) into the action of the Buttons, however 1) It didn't scroll to the selection I wanted 2) When I tapped on the "•", it was as if I was tapping on all of them because they all did the tapping animation 3) I wasn't able to divide the List as I wanted to.
I have this:
import SwiftUI
struct ContentView: View {
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", "X","Y", "Z"]
let values = ["Avalue", "Bvalue", "Cvalue", "Dvalue"]
var body: some View {
ScrollViewReader{ scrollviewr in
ZStack {
ScrollView(.vertical) {
VStack {
ForEach(alphabet, id: \.self) { letters in
Button(letters){
withAnimation {
scrollviewr.scrollTo(letters)
}
}
}
}
}.offset(x: 180, y: 120)
VStack {
ForEach(values, id: \.self){ vals in
Text(vals).id(vals)
}
}
}
}
}
}
But I'd want it like this:
import SwiftUI
struct AlphabetSort2: View {
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", "X","Y", "Z"]
let values = ["Avalue", "Bvalue", "Cvalue", "Dvalue", "Mvalue", "Zvalue"]
var body: some View {
ScrollView {
ScrollViewReader { value in
ZStack{
List{
ForEach(alphabet, id: \.self) { letter in
Section(header: Text(letter)) {
ForEach(values.filter { $0.hasPrefix(letter) }, id: \.self) { vals in
Text(vals).id(vals)
}
}.id(letter)
}
}
HStack{
Spacer()
VStack {
ForEach(0..<alphabet.count, id: \.self) { idx in
Button(action: {
withAnimation {
value.scrollTo(alphabet[idx])
}
}, label: {
Text(idx % 2 == 0 ? alphabet[idx] : "\u{2022}")
})
}
}
}
}
}
}
}
}
struct AlphabetSort2_Previews: PreviewProvider {
static var previews: some View {
AlphabetSort2()
}
}
Add an swipe-up-and-down action for the alphabet jumper, so we can get an UILocalizedIndexCollation -like swipe effect, it works for view and add mode, but delete, I guess it is due to SwiftUI's UI refresh mechanism.
extension String {
static var alphabeta: [String] {
var chars = [String]()
for char in "abcdefghijklmnopqrstuvwxyz#".uppercased() {
chars.append(String(char))
}
return chars
}
}
struct Document: View {
#State var items = ["Alpha", "Ash", "Aman", "Alisia", "Beta", "Baum", "Bob", "Bike", "Beeber", "Beff", "Calipha", "Cask", "Calf", "Deamon", "Deaf", "Dog", "Silk", "Seal", "Tiger", "Tom", "Tan", "Tint", "Urshinabi", "Verizon", "Viber", "Vein", "Wallet", "Warren", "Webber", "Waiter", "Xeon", "Young", "Yoda", "Yoga", "Yoger", "Yellow", "Zeta"]
var body: some View {
ScrollViewReader { scrollView in
HStack {
List {
ForEach(String.alphabeta, id: \.self){ alpha in
let subItems = items.filter({$0.starts(with: alpha)})
if !subItems.isEmpty {
Section(header: Text(alpha)) {
ForEach(subItems, id: \.self) { item in
Text(item)
}.onDelete(perform: { offsets in
items.remove(at: offsets.first!)
})
}.id(alpha)
}
}
}
VStack{
VStack {
SectionIndexTitles(proxy: scrollView, titles: retrieveSectionTitles()).font(.footnote)
}
.padding(.trailing, 10)
}
}
}
// .navigationBarTitleDisplayMode(.inline)
// .navigationBarHidden(true)
}
func retrieveSectionTitles() ->[String] {
var titles = [String]()
titles.append("#")
for item in self.items {
if !item.starts(with: titles.last!){
titles.append(String(item.first!))
}
}
titles.remove(at: 0)
if titles.count>1 && titles.first! == "#" {
titles.append("#")
titles.removeFirst(1)
}
return titles
}
}
struct Document_Previews: PreviewProvider {
static var previews: some View {
Document()
}
}
struct SectionIndexTitles: View {
class IndexTitleState: ObservableObject {
var currentTitleIndex = 0
var titleSize: CGSize = .zero
}
let proxy: ScrollViewProxy
let titles: [String]
#GestureState private var dragLocation: CGPoint = .zero
#StateObject var indexState = IndexTitleState()
var body: some View {
VStack {
ForEach(titles, id: \.self) { title in
Text(title)
.foregroundColor(.blue)
.modifier(SizeModifier())
.onPreferenceChange(SizePreferenceKey.self) {
self.indexState.titleSize = $0
}
.onTapGesture {
proxy.scrollTo(title, anchor: .top)
}
}
}
.gesture(
DragGesture(minimumDistance: indexState.titleSize.height, coordinateSpace: .named(titles.first))
.updating($dragLocation) { value, state, _ in
state = value.location
scrollTo(location: state)
}
)
}
private func scrollTo(location: CGPoint){
if self.indexState.titleSize.height > 0{
let index = Int(location.y / self.indexState.titleSize.height)
if index >= 0 && index < titles.count {
if indexState.currentTitleIndex != index {
indexState.currentTitleIndex = index
print(titles[index])
DispatchQueue.main.async {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
// withAnimation {
proxy.scrollTo(titles[indexState.currentTitleIndex], anchor: .top)
// }
}
}
}
}
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
struct SizeModifier: ViewModifier {
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
}
}
func body(content: Content) -> some View {
content.background(sizeView)
}
}
I've the following code:
VStack (spacing: 0) {
ForEach (0..<myMaze.height, id: \.self) { (y: Int) in
HStack (spacing: 0) {
ForEach (0..<myMaze.width, id: \.self) { (x: Int) in
DrawRoom(xPos: x, yPos: y)
} // ForEach x
} // HSTack
} // ForEach y
} // VSTack
and
struct DrawRoom: View {
#ObservedObject var myMaze = Maze.sharedInstance
var xPos: Int
var yPos: Int
var body: some View {
Square(value: myMaze.value)
.stroke(Color.gray, style: StrokeStyle(lineWidth: 3))
.frame(width: myMaze.sideLength, height: myMaze.sideLength)
}
init(xPos: Int, yPos: Int) {
self.xPos = xPos
self.yPos = yPos
myMaze.value = myMaze.mazeData[xPos][yPos]
print("DrawRoom: [\(xPos), \(yPos)]")
}
}
... with the following output in the console:
X, Y
DrawRoom: [0, 2]
DrawRoom: [1, 2]
DrawRoom: [2, 2]
DrawRoom: [0, 0]
DrawRoom: [1, 0]
DrawRoom: [2, 0]
DrawRoom: [0, 1]
DrawRoom: [1, 1]
DrawRoom: [2, 1]
Surprisingly the "Y" starts at 2 then 0 and finally 1 but it should be as follows:
X, Y
DrawRoom: [0, 0]
DrawRoom: [1, 0]
DrawRoom: [2, 0]
DrawRoom: [0, 1]
DrawRoom: [1, 1]
DrawRoom: [2, 1]
DrawRoom: [0, 2]
DrawRoom: [1, 2]
DrawRoom: [2, 2]
Can somebody explain me why, is there something wrong in my coding?
For info, "Square" draws lines according to the value of myMaze.mazeData[xPos][yPos]
See below the whole code:
//
// Maze.swift
// NewMaze
//
// Created by Philippe Lagarrigue on 26/12/2020.
//
import Foundation
import SwiftUI
let side = ["North", "East", "South", "West"]
let direction = [1, 2, 4, 8]
let wall = (North: 1, East: 2, South: 4, West: 8) // 15 means all walls
let exit = (toNorth: 16, toEast: 32, toSouth: 64, toWest: 128) // 0 means no exit
struct Room {
var x: Int
var y: Int
var roomsToExit: Int
}
class Maze: ObservableObject {
// Properties
static let sharedInstance = Maze()
var viewWidth = CGFloat(0.0) //{ didSet { update(flag: 2) } }
var viewHeight = CGFloat(0.0) //{ didSet { update(flag: 2) } }
var ready2Draw = false
var width = 0 // Number of rooms (horizontally)
var height = 0 // Number of rooms (vertically)
#Published var widthDouble = CGFloat(3.0) { didSet { ready2Draw = false } }
#Published var heightDouble = CGFloat(3.0) { didSet { ready2Draw = false } }
var oneWay = true
var exitRoom = Room(x: 0, y: 0, roomsToExit: 0)
var farestRoom = Room(x: 0, y: 0, roomsToExit: 0)
var currentPos = Room(x: 0, y: 0, roomsToExit: 0)
var mazeData = [[Int]]()
//var width: Int { return Int(widthDouble) } // Number of rooms (horizontally)
//var height: Int { return Int(heightDouble) } // Number of rooms (vertically)
var numberOfRooms: Int { return self.width * self.height }
var sideLength = CGFloat(40.0) //{ didSet { print("sideLength: \(sideLength)") } }
var value = 15 // { didSet { print("value: \(value)") } }
func defineExit() {
let choosenSide = Int.random(in: 0...3) // N, E, S or W
// Fill array with 15 which means that all walls are set
mazeData = Array(repeating: Array(repeating: 15, count: self.height), count: self.width)
//var count = 0
//for x in 0..<width {
// for y in 0..<height {
// print(x, y, count, count/height, count%height)
// mazeData[x][y] = 15
// count += 1
// }
//}
print("\nchoosenSide: \(side[choosenSide]) (\(choosenSide))")
if (choosenSide + 1) % 2 == 0 {
print("Axe: E-W")
exitRoom.x = choosenSide == 3 ? 0 : self.width - 1
exitRoom.y = Int.random(in: 1..<self.height)
} else {
print("Axe: N-S")
exitRoom.x = Int.random(in: 1..<self.width)
exitRoom.y = choosenSide == 2 ? self.height - 1 : 0
}
currentPos.x = exitRoom.x
currentPos.y = exitRoom.y
currentPos.roomsToExit = 1
//print("The exit room is at \(exitRoom.x), \(exitRoom.y)")
mazeData[currentPos.x][currentPos.y] = 15 - direction[choosenSide]
print("The exit room is at [\(exitRoom.x), \(exitRoom.y)] => \(mazeData[currentPos.x][currentPos.y])")
}
init() {
//buildMaze()
}
func update(flag: Int) {
if flag & 1 == 1 {
width = Int(widthDouble)
height = Int(heightDouble)
}
if flag & 2 == 2 {
let viewW = viewWidth/widthDouble
let viewH = viewHeight/heightDouble
let side = (viewW < viewH ? viewW : viewH) - 1.0
self.sideLength = side - side * 0.1
print("Maze init:", width, "x", height, "=", numberOfRooms)
}
ready2Draw = false
}
func dummy() {
let wd = widthDouble
let hd = heightDouble
widthDouble = 3
heightDouble = 3
update(flag: 3)
defineExit()
widthDouble = wd
heightDouble = hd
}
func buildMaze(){
dummy() // This is the only way I found to get the maze displayed correctly
update(flag: 3)
defineExit()
//var rooms = numberOfRooms
//while (rooms > 0) {
// newRoom()
// rooms -= 1
//}
ready2Draw = true
}
func newRoom() {
}
}
//
// ContentView.swift
// NewMaze
//
// Created by Philippe Lagarrigue on 26/12/2020.
//
import SwiftUI
import Combine // << needed for Just publisher below
struct ContentView: View {
#ObservedObject var myMaze: Maze = .init()
#State private var selectedView = 0
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
TabView(selection: $selectedView) {
NavigationView() .tabItem { Image("navigation"); Text("Navigation") }.tag(0)
.onReceive(timer) { input in selectedView = 1; self.timer.upstream.connect().cancel() }
SettingsView() .tabItem { Image("settings"); Text("Settings") }.tag(1)
MapView() .tabItem { Image("map"); Text("Map") }.tag(2)
}
//.onReceive(Just(selectedView)) { print($0) }
//.onLongPressGesture { myMaze.ready2Draw = true }
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct NavigationView : View {
#ObservedObject var myMaze = Maze.sharedInstance
var body : some View {
GeometryReader { geometry in
Path { path in
myMaze.viewWidth = geometry.size.width
myMaze.viewHeight = geometry.size.width
}
}
}
}
struct Square: Shape {
//#ObservedObject var myMaze = Maze.sharedInstance
var value: Int
func path(in rect: CGRect) -> Path {
var path = Path()
if (value & wall.North == wall.North) {
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
}
if (value & wall.East == wall.East) {
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
}
if (value & wall.South == wall.South) {
path.move(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
}
if (value & wall.West == wall.West) {
path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
}
return path
}
init(value: Int) {
self.value = value
//if value != 15 {
// print("Square: \(value)")
//}
}
}
struct DrawRoom: View {
#ObservedObject var myMaze = Maze.sharedInstance
var xPos: Int
var yPos: Int
let numbers = false
var body: some View {
if numbers {
Text("\(myMaze.value, specifier: "%.02d")")
.font(.system(size: 18, design: .monospaced))
.foregroundColor(myMaze.value == 15 ? Color.black : Color.red)
} else {
Square(value: myMaze.value)
.stroke(Color.gray, style: StrokeStyle(lineWidth: 3))
.frame(width: myMaze.sideLength, height: myMaze.sideLength)
}
}
init(xPos: Int, yPos: Int) {
self.xPos = xPos
self.yPos = yPos
myMaze.value = myMaze.mazeData[xPos][yPos]
print("DrawRoom: [\(xPos), \(yPos)]")
}
}
struct MapView : View {
#ObservedObject var myMaze = Maze.sharedInstance
var body : some View {
if myMaze.ready2Draw {
VStack (spacing: 0) {
ForEach (0..<myMaze.height, id: \.self) { (y: Int) in
HStack (spacing: 0) {
ForEach (0..<myMaze.width, id: \.self) { (x: Int) in
DrawRoom(xPos: x, yPos: y)
} // ForEach x
} // HSTack
} // ForEach y
} // VSTack
} else {
Button(action: { action() }) { Text("Build maze") }
.padding()
.background(Color.red)
.cornerRadius(20)
.foregroundColor(.white)
.font(.title)
.shadow(color: .gray, radius: 20.0, x: 10, y: 10)
}
} // body
func action() {
myMaze.buildMaze()
}
}
struct SettingsView : View {
#ObservedObject var myMaze = Maze.sharedInstance
#State private var isEditing1 = false
#State private var isEditing2 = false
var body : some View {
HStack(alignment: VerticalAlignment.top) {
VStack(alignment: .leading) {
Text("Settings\n") .font(.title)
Text("Number of rooms (horizontally): \(myMaze.widthDouble, specifier: "%.0f")")
.foregroundColor(isEditing1 ? .red : .black)
Slider(value: $myMaze.widthDouble, in: 3...32, step: 1,
onEditingChanged: { editing in isEditing1 = editing })
Text("\nNumber of rooms (vertically): \(myMaze.heightDouble, specifier: "%.0f")")
.foregroundColor(isEditing2 ? .red : .black)
Slider(value: $myMaze.heightDouble, in: 3...32, step: 1,
onEditingChanged: { editing in isEditing2 = editing })
Text("\nIn a maze with only one way, you can find the exit by touching the wall or hedge with the hand nearest to it, left or right. Keep that same hand touching the wall and keep walking. This may take you on a horribly long route, but it will get you out.")
Toggle("One way", isOn: $myMaze.oneWay)
Spacer()
Button(action: { action() }) { Text("Build maze") }
.padding()
.background(Color.red)
.cornerRadius(20)
.foregroundColor(.white)
.font(.title)
.shadow(color: .gray, radius: 20.0, x: 10, y: 10)
Spacer()
} .padding()
}
}
func action() {
myMaze.buildMaze()
}
}
one or the other works but not both
if the timers have the same interval it works
struct ContentView: View {
#State var isPlaying: Bool = true
#State var timeRemaining: Int = 1800
#State var count = 1
#State var count2 = 10
var body: some View {
let timer2 = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
let timer = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
VStack {
Text("\(count) \(count2)")
.padding()
}
.onReceive(timer2) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count2 += 1
}
}
.onReceive(timer) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count += 1
}
}
}
}
can anyone suggest any changes to my code?
Move the timers out of the body and define them as properties of the View:
struct ContentView: View {
#State var isPlaying: Bool = true
#State var timeRemaining: Int = 1800
#State var count = 1
#State var count2 = 10
let timer2 = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
let timer = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
var body: some View {
VStack {
Text("\(count) \(count2)")
.padding()
}
.onReceive(timer2) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count2 += 1
}
}
.onReceive(timer) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count += 1
}
}
}
}
This gives me some very unusual results
struct ContentView: View {
#State var isPlaying: Bool = true
#State var timeRemaining: Int = 1800
#State var count = 1
#State var count2 = 10
#State var rate = 0.5
let timer = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
var body: some View {
let timer2 = Timer.publish(every: rate, on: .main, in: .default).autoconnect()
VStack {
Text("\(count) \(count2)")
.padding()
}
.onReceive(timer) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count += 1
}
}
.onReceive(timer2) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count2 += 1
}
}
}
}
despite the intervals being the same, the counters increase at different rates, even randomly
I've been working on a truth or dare app, in which you spin a wheel and you randomly get a truth or dare, and I want to have a bunch of random truth questions and dares saved in a database.
I have it set up so when you press the spin button it randomly chooses a truth or dare out of an array.
i've tried it a little and couldn't seem to get it to work. so my question is would it be possible to do something like,
if self.number[0] == 0 || 2 || 3{ (this is where it would get the random truth)}
else{(this is where it would get the random dare)}
I don't know if it will help, but this is the rest of my code.
import SwiftUI
import CoreHaptics
class Colors{
static var drinkGradient = [LinearGradient(gradient: Gradient(colors: [Color("drinkcard"), Color("drinkcard2"), Color("drinkcard")]), startPoint: .top, endPoint: .bottom)]
static var truthGradient = [LinearGradient(gradient: Gradient(colors: [Color("truthcard"), Color("truthcard"), Color("truthcard")]), startPoint: .top, endPoint: .bottom)]
static var dareGradient = [LinearGradient(gradient: Gradient(colors: [Color("darecard"), Color("darecard"), Color("darecard")]), startPoint: .top, endPoint: .bottom)]
}
struct ContentView: View {
#State private var timer:Timer!
#State private var text = [String("Truth"), String("Dare"), String("Truth"), String("Dare"), String("Truth"), String("Dare"), String("Drink!!")]
#State private var foregrounds = [Color.white, Color.white, Color.white, Color.white, Color.white, Color.white, Color.black]
#State private var number = [0]
#State private var timeKeeper: Float = 0
#State private var angle: Double = 0
let generator = UINotificationFeedbackGenerator()
#State var backgrounds = [Colors.truthGradient[0], Colors.dareGradient[0], Colors.truthGradient[0], Colors.dareGradient[0], Colors.truthGradient[0], Colors.dareGradient[0], Colors.drinkGradient[0]]
var body: some View {
ZStack{
Rectangle()
.foregroundColor(Color("background"))
.edgesIgnoringSafeArea(.all)
VStack{
HStack {
Text("Truth, Dare or Drink")
.shadow(radius: 10)
.padding(.top, 20)
.foregroundColor(.white)
.font(.system(.largeTitle, design: .rounded) )
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 100)
.background(Color("banner"))
.edgesIgnoringSafeArea(.top)
.shadow(radius: 15)
}
Text("This is where the Truth or dare will go.")
.frame(width: 350, height: 200)
.foregroundColor(.white)
.font(.system(.headline, design: .rounded))
Spacer()
Text(text[number[0]])
.font(.system(.largeTitle, design: .rounded))
.foregroundColor(foregrounds[number[0]])
.frame(width: 250, height: 250)
.background(backgrounds[number[0]])
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(radius: 10)
.rotationEffect(.degrees(angle))
.animation(.easeIn)
Spacer()
Button(action: {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true, block: {_ in
self.timeKeeper += 0.10
if self.timeKeeper < 3{
if self.number[0] == self.text.count - 1{
self.number[0] = 0
self.angle += 360
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
else{
self.number[0] += 1
self.angle += 360
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
}
else{
self.number[0] = Int.random(in:
0...self.text.count - 1)
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
self.angle += 360
self.timeKeeper = 0
self.timer.invalidate()
}
})
}) {
Text("Spin")
.font(.system(.title, design: .rounded))
.foregroundColor(.white)
.frame(width: 250, height: 50)
.background(Color("button"))
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(radius: 15)
}
Spacer()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
if (self.number[0] == 0 || self.number[0] == 2 || self.number[0] == 3) {
print("truth")
} else {
print("dare")
}
I am using a timer in a SwiftUI view as in code below. It works as expected BUT under some conditions I want to cancel/stop that timer. There seems to be no ".cancel" property or method on the timer var. How do I cancel this timer? Any ideas/tips?
import SwiftUI
struct ContentView: View {
#State private var selection = 2
#State private var rotation: Double = GBstrtest
let timer = Timer.publish (every: 0.8, on: .current, in: .common).autoconnect()
var body: some View {
TabView(selection: $selection){
Text("Settings")
.font(.title)
.tabItem {
VStack {
Image(systemName: "gear")
.font(Font.system(.title ))
Text("Settings")
}
}
.tag(0)
VStack {
Divider().padding(2)
ZStack {
Image("image1")
.resizable()
.aspectRatio(contentMode: .fit)
Image("image2")
.resizable()
.aspectRatio(contentMode:.fit)
.rotationEffect(.degrees(rotation))
.animation(.easeInOut(duration: 0.3) )
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
}
Spacer()
}
.tabItem {
VStack {
Image(systemName: "speedometer")
.font(Font.system(.title ))
Text("Read Meter")
}
}
.tag(1)
}
.onReceive(timer) {
_ in self.rotation = Double.random(in: 0 ... 200)
// How do I cancel timer HERE!?
}
}
}
Inside your conditional statement, use the following code:
self.timer.upstream.connect().cancel()
Full cycle goes like this:
struct MySwiftUIView : View {
...
#State var timer = Timer.publish (every: 1, on: .current, in: .common).autoconnect()
#State var timeRemaining = -1
var body: some View {
ZStack {
// Underlying shapes etc as needed
Text("\(timeRemaining)").
.opacity(timeRemaining > 0 ? 1 : 0)
.onReceive(timer) { _ in
if self.timeRemaining < 0 {
// We don't need it when we start off
self.timer.upstream.connect().cancel()
return
}
if self.timeRemaining > 0 {
self.timeRemaining -= 1
} else {
self.timer.upstream.connect().cancel()
// Do the action on end of timer. Text would have been hidden by now
}
}
.onTapGesture { // Or any other event handler that should start countdown
self.timeRemaining = Int(delayValue)
self.timer = Timer.publish (every: 1, on: .current, in:
.common).autoconnect()
}
}
Voila! A reusable timer, use it as many times as you'd like!