ObservableObject and Condition - timer

I have a code for a timer that starts automatically and should stop again with an if statement. Unfortunately, that doesn't work and I don't get an error message. what am I doing wrong here?
import SwiftUI
import Combine
import Foundation
class WaitingTimerClass: ObservableObject {
#Published var waitingTimerCount: Int = 0
var waitingTimer = Timer()
func start() {
self.waitingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in self.waitingTimerCount += 1 } }
func stop() { waitingTimer.invalidate() }
func reset() { waitingTimerCount = 0; waitingTimer.invalidate() }
}
struct ContentView: View {
#ObservedObject var observed = WaitingTimerClass()
var body: some View {
Text("\(self.observed.waitingTimerCount)")
.onAppear {
self.observed.start()
if self.observed.waitingTimerCount == 3 {
self.observed.stop()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
``

The code in onAppear block works only once, when the View appears (or several times, when view disappear and appear again). In your case waitingTimerCount will always be 0. If you need to observe some value from the view use onReceive. The code should looks like this:
struct TimerWaiter: View {
#ObservedObject var observed = WaitingTimerClass()
var body: some View {
Text("\(self.observed.waitingTimerCount)")
.onAppear {
self.observed.start()
}
.onReceive(observed.$waitingTimerCount) { count in
guard count == 3 else { return }
self.observed.stop()
}
}
}
update for first author's comment: I can't figure out, why reseting timer is not working as expected and I have not much time today. Maybe tomorrow it will be easier to understand. Here is not quite good, but worked solution:
class WaitingTimerClass: ObservableObject {
#Published var waitingTimerCount: Int = 0
var didInvalidate = PassthroughSubject<Void, Never>()
var waitingTimer = Timer()
func start() {
self.waitingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in self.waitingTimerCount += 1 } }
func stop() { waitingTimer.invalidate() }
func reset() {
waitingTimer.invalidate()
waitingTimerCount = 0
didInvalidate.send()
}
}
struct TimerWaiter: View {
#ObservedObject var observed = WaitingTimerClass()
#State private var count = 0
var body: some View {
Text("\(count)")
.onAppear {
self.observed.start()
}
.onReceive(observed.$waitingTimerCount) { count in
self.count = count
guard count == 3 else { return }
self.observed.reset()
}
.onReceive(observed.didInvalidate) { _ in
self.count = 0
}
}
}
P.S. I will try to dig deeper to understand, why the last changing of waitingTimerCount does not affect View

.onAppear {
self.observed.start()
if self.observed.waitingTimerCount == 3 {
self.observed.stop()
}
}
How this code works?
it is running when the View Appears (most likely only once)
start the timer
checked if waitinigTimer == 3 // it is not 3 at this moment
return
So, it simply never stop, as you already know.
If you like to stop it if waitingTimer == 3, you have to do it in your model.
#Published var waitingTimerCount: Int = 0 {
willSet {
if newValue == 3 { stop() }
}
}

Thank you very much. the solution to the reset problem can be found here:
ObservableObject bug or not

Related

Sort the array alphabetically. Output an array in sections split alphabetically. SwiftUI [duplicate]

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

Why is this array clearing

I have an issue where I am calling a function, it's appending an array, and then I get a random element from it, the problem is that is keeps getting wiped right after the function is called before I can use it, therefore, the random element request is causing a fatal error.
Utilities.getKnockKnockJokes {
self.previousKnockKnockJoke = self.currentKnockKnockJoke
print("currentKnockKnockJoke = \(self.currentKnockKnockJoke)")
self.currentKnockKnockJoke = KnockKnockJokes.knockKnockJokes.randomElement()!
print("newCurrentKnockKnockJoke = \(self.currentKnockKnockJoke)")
self.singleServeText.text = self.currentKnockKnockJoke
}
The function called is below:
static func getKnockKnockJokes(completion: #escaping () -> Void) {
let db = Firestore.firestore()
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: .background).async {
print("countKnockKnockJokes = \(self.countKnockKnockJokes)")
if self.countKnockKnockJokes == 0 {
while self.countKnockKnockJokes == 0 {
if self.countKnockKnockJokes == 0 {
self.countKnockKnockJokes = 1
group.leave()
}else {
print("leaving")
group.leave()
}
}
}else {
print("skipped")
group.leave()
}
}
group.notify(queue: .main) {
db.collection("jokes").document("Knock Knock Jokes").addSnapshotListener { document, error in
//check for error
if error == nil {
//check if document exists
if document != nil && document!.exists {
if let JokeNum = document!.get("JokeNum") as? Int {
self.countKnockKnockJokes = JokeNum
UserDefaults.standard.setValue(JokeNum, forKey: "countKnockKnockJokes")
print("KnockKnockJokeNum = \(self.countKnockKnockJokes)")
}
var count = 1
print("count = \(count)/\(self.countKnockKnockJokes)")
print("countKnockKnockJoke = \(self.countKnockKnockJokes)")
//Utilities.knockKnockJokes.removeAll()
KnockKnockJokes.knockKnockJokes.removeAll()
for _ in 0...self.countKnockKnockJokes {
print("count = \(count)/\(self.countKnockKnockJokes)")
if let Joke = document!.get("\(count)") as? String {
print("KnockKnockJokeNum = \(self.countKnockKnockJokes)")
if Utilities.jokes.contains("\(Joke) - From Knock Knock Jokes") {}else {
print("Joke = \(Joke)")
Utilities.jokes.append("\(Joke) - From Knock Knock Jokes")
KnockKnockJokes.knockKnockJokes.append(Joke)
print("KnockKnockJokes = \(KnockKnockJokes.knockKnockJokes)")
UserDefaults.standard.set(KnockKnockJokes.knockKnockJokes, forKey: defaults.knockKnockJokes.rawValue)
Utilities.updateJokesDefaults()
}
print("countKnockKnockFinal = \(count)/\(self.countKnockKnockJokes)")
if count == self.countKnockKnockJokes {
completion()
}
count = count + 1
}
}
}
}
}
}
}
try this:
if count == self.countKnockKnockJokes {
completion()
return // <--- here
}
I fixed it, my remove all was running after all the appends.

How can I automatically update TextField in swiftUI

I'm new in swiftUI developing, and I have a little problem. I made a function using Timer to create a count up timer. I want to display the time updating in a TextField, but it doesn't work (with a Text it works). Here is the code:
import SwiftUI
struct ContentView: View {
#State var min: Int = 0
#State var sec: Int = 0
#State var time = ""
#State var timer: Timer? = nil
var body: some View {
NavigationView{
VStack{
TextField("Tempo (min o mm:ss)", text: $time)
HStack{
Button(action: {
self.start()
self.time = String(self.min)+":"+String(self.sec)
}){
Text("Start")
}
Button(action: {
self.stop()
}){
Text("Stop")
}
}
}//VStack
}//NavigationView
}//body
func start(){
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){
temp in
self.sec = self.sec + 1
if(self.sec == 59){
self.sec = 0
self.min = self.min + 1
}
}
}
func stop(){
timer?.invalidate()
timer = nil
}
}//contetView
You don't see the change because you're not updating the time String in your timer loop.
Here's the fix:
func updateTimeString() {
self.time = String(format: "%02d:%02d", self.min, self.sec)
}
func start(){
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { temp in
self.sec = self.sec + 1
if self.sec == 59 {
self.sec = 0
self.min = self.min + 1
}
updateTimeString()
}
}
Notes:
I moved the code to set the time string to a separate function. You can then call this from your Button as well.
I used String(format:) to add formatting to your string to show 2 digits for the minutes and seconds.

SwiftUI: How to cancel timer in SwiftUI view?

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!

How to make struct a subscriptable in Swift 2?

Within my struct I have the following:
subscript(index: Int) -> FileSystemObject {
var i: Int = 0
for a in contents! {
if (i == index) {
return a
}
i++
}
}
Where,
var contents: FileSystemObject = [FileSystemObject]?
But when,
let it: FileSystemObject = FileSystemObject()
And I write:
return it.contents![index]
I receive the error
Cannot subscript a value of type [FileSystemObject]
What am I doing wrong here?
Additionally, note that:
Changing each of the objects with the value
FileSystemObject
To,
[FileSystemObject]
Does not help.
EDIT 1:
This is the entirety of the code:
MainWindowController.swift
class MainWindowController: NSWindowController, NSOutlineViewDataSource, NSOutlineViewDelegate {
#IBOutlet weak var sourceView: NSOutlineView!
static var fileManager: NSFileManager = NSFileManager.defaultManager()
static var fileSystem: FileSystemObject = FileSystemObject(path: "/", fs: fileManager)
var outlineSource: OutlinePrep = OutlinePrep(fs: fileSystem)
override func windowDidLoad() {
super.windowDidLoad()
sourceView.setDataSource(self)
sourceView.setDelegate(self)
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
guard let it = item as? OutlinePrep else {
return outlineSource.basePath
}
return it.data[index]
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
// return (item == nil) ? YES : ([item numberOfChildren] != -1);
print(item)
guard let it = item as? OutlinePrep else {
return false
}
for (var i: Int = 0; i < it.data.count; i++) {
guard let _ = it.data[i].contents else {
return false
}
}
return true
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
guard let it = item as? OutlinePrep else {
return outlineSource.data.count
}
var i: Int = 0
for a in it.data {
guard let _ = a.contents else {
continue
}
i++
}
return i
}
}
FileSystem.swift
struct FileSystemObject {
let basePath: String
let name: String
var isDir = ObjCBool(false)
var contents: [FileSystemObject]?
init(path: String, fs: NSFileManager) {
basePath = path
let root: [String]
fs.fileExistsAtPath(path, isDirectory: &isDir)
if (isDir.boolValue) {
do {
root = try fs.contentsOfDirectoryAtPath(path)
}
catch {
root = ["Error"]
}
contents = []
for r in root {
contents!.append(FileSystemObject(path: (path + (r as String) + "/"), fs: fs))
}
}
name = path
}
subscript(index: Int) -> FileSystemObject {
get {
let error: FileSystemObject = FileSystemObject(path: "", fs: NSFileManager.defaultManager())
guard let _ = contents else {
return error
}
var i: Int = 0
for a in contents! {
if (i == index) {
return a
}
i++
}
return error
}
set {
}
}
}
Outline.swift
struct OutlinePrep {
var data: [FileSystemObject]
let basePath: String
private var cell: Int = -1
init (fs: FileSystemObject) {
data = fs.contents!
basePath = fs.basePath
}
mutating func outlineDelegate() -> String {
cell++
return data[cell].name
}
func testFunc(data: [FileSystemObject]) {
for (var i: Int = 0; i < data.count; i++) {
guard let d = data[i].contents else {
print(data[i].name)
continue
}
testFunc(d)
}
}
}
EDIT 2:
To clarify, I am inquiring as to how I might resolve the error, as all other provided code works as intended.
The error message is misleading. The problem becomes more apparent
if you split
return it.data[index]
into two separate statements:
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
guard let it = item as? OutlinePrep else {
return outlineSource.basePath
}
let fso = it.data[index]
return fso // error: return expression of type 'FileSystemObject' does not conform to 'AnyObject'
}
The value of it.data[index] is a
FileSystemObject, which is a struct and therefore it
does not conform to AnyObject and cannot be the return value
of that method. If you want to return a FileSystemObject
then you have to define that as a class instead.
simplified ...
struct FileSystemObject {
var context: [FileSystemObject]?
init() {
context = []
// your init is called recursively, forever ...
let fso = FileSystemObject()
context?.append(fso)
}
}
let s = FileSystemObject()

Resources