How to toggle a boolean value using timer in Swift - swift

I have a boolean called 'isMatched'. It is written in my Model. I use this boolean to show a view on a certain state of the view. However, I want the view to get hidden automatically after 0.5 second. I tried to use DispatchQueue.main.asyncAfter and also Timer, but always got error "Escaping closure captures mutating 'self' parameter". I have copied my Model below. Please advice me a solution.
I made the boolean var 'isMatched' true in the 'choose()' function.
import Foundation
struct WordMatchingGameModel<CardContent> {
private(set) var cards: Array<Card>
private(set) var words: Array<Word>
private(set) var gameDataSet: Array<String>
private(set) var matchedWord: String = ""
var isMatched: Bool = false
private var colors: [[String]] = [["AeroBlue", "AeroBlue"], ["BlueBell", "BlueBell"], ["PinkLavender", "PinkLavender"], ["Opal", "Opal"], ["CornflowerBlue", "CornflowerBlue"]]
mutating func choose(card: Card) {
if let choosenIndex = cards.firstIndex(matching: card) {
if !cards[choosenIndex].isTapped {
cards[choosenIndex].isTapped = true
cards[choosenIndex].bgColors = numberOfMatchedWords == 0 ? colors[0] : numberOfMatchedWords == 1 ? colors[1] : colors[2]
words.append(Word(id: cards[choosenIndex].id, content: cards[choosenIndex].content))
print(word)
if match() {
for index in 0..<cards.count {
if cards[index].isTapped { cards[index].isDisabled = true }
}
numberOfMatchedWords += 1
score += 10
matchedWord = word
isMatched = true
delay(interval: 0.5) {
self.isMatched = false
}
words.removeAll()
}
} else {
cards[choosenIndex].isTapped = false
cards[choosenIndex].bgColors = ["GradientEnd", "GradientStart"]
var tempWords = Array<Word>()
for index in 0..<words.count {
if words[index].id != cards[choosenIndex].id { tempWords.append(words[index]) }
}
words = tempWords
}
}
}
func delay(interval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval, execute: closure)
}
func match() -> Bool {
for index in 0..<gameDataSet.count {
if gameDataSet[index] == word {
return true
}
}
return false
}
var word: String {
var array = Array<Character>()
for index in 0..<words.count {
array.append(words[index].content as! Character)
}
return String(array).capitalized
}
private(set) var numberOfMatchedWords: Int = 0
private(set) var score: Int = 0
init(numberOfCards: Int, gameDataSet: Array<String>, cardContentFactory: (Int) -> CardContent) {
cards = Array<Card>()
words = Array<Word>()
self.gameDataSet = gameDataSet
for index in 0..<numberOfCards {
let content = cardContentFactory(index)
self.cards.append(Card(id: index + 1, content: content))
}
cards.shuffle()
colors.shuffle()
}
struct Card: Identifiable {
var id: Int
var content: CardContent
var bgColors: [String] = ["GradientEnd", "GradientStart"]
var isTapped: Bool = false
var isDisabled: Bool = false
}
struct Word: Identifiable {
var id: Int
var content: CardContent
}
}

Create a reference for self inside the function and use that instant of self inside the delay function.
mutating func choose(card: Card) {
var createReference = self
....................
delay(interval: 0.5) {
createReference.isMatched = false
}
}

Related

Converting multiple Bools to String for CoreData storage and back again

I have a situation where I have 20-30 true/false questions that need to be stored in CoreData. Instead of creating 20-30 different objects in CoreData I would like to convert the answers to a String (or other if easier) so I can save as one Object. I have a working model, but feel like I took the long way around. I'm sure there's a more efficient way of doing this, it gets dicey when adding all results. I'm not the most experienced, so I may be missing something basic, excuse me if that is the case. Thanks for the help!
Conversion to save to CoreData as String
struct ConversionForCD: View {
#State var firstAnswer: Bool = false
#State var secondAnswer: Bool = false
#State var thirdAnswer: Bool = false
#State var fourthAnswer: Bool = false
#State var coreDataObject: String = ""
var body: some View {
Form {
Toggle("First Question", isOn: $firstAnswer)
Toggle("Second Question", isOn: $secondAnswer)
Toggle("Third Question", isOn: $thirdAnswer)
Toggle("Fourth Question", isOn: $fourthAnswer)
Button("Save To CoreData") {
saveToCoreData()
}
Text(coreDataObject)
}
}
func boolToString(first: Bool,
second: Bool,
third: Bool,
fourth: Bool) -> String {
let firstString = first.intValue()
let secondString = second.intValue()
let thirdString = third.intValue()
let fourthString = fourth.intValue()
return "\(firstString)"+"\(secondString)"+"\(thirdString)"+"\(fourthString)"
}
func saveToCoreData() {
coreDataObject = boolToString(
first: firstAnswer,
second: secondAnswer,
third: thirdAnswer,
fourth: fourthAnswer)
}
}
extension Bool: IntValue {
func intValue() -> Int {
if self {
return 1
}
return 0
}
}
Display fetched results from CoreData (Edited)
struct ConversionFromCD: View {
#State var coreDataString: String = "1001"
#State var result: [Bool] = []
var body: some View {
Form {
Button("Displaye Results From CD") {
result = stringToBool(boolString: coreDataString)
}
ForEach(result, id: \.self) { value in
Text(value.description)
}
}
}
func stringToBool(boolString: String) -> Array<Bool> {
for ch in boolString {
result.append((String(ch) as NSString).boolValue)
}
return result
}
extension String {
var boolValue: Bool {
return (self as NSString).boolValue
}}
A reasonable solution is to encode/decode the Bool array from and to JSON.
In the NSManagedObject subclass add a Core Data attribute stateString
#NSManaged public var stateString: String
and a computed property
var toggleStates : [Bool] {
get { return (try? JSONDecoder().decode([Bool].self, from: Data(stateString.utf8))) ?? [] }
set {
let data = try! JSONEncoder().encode(newValue)
stateString = String(data: data, encoding: .utf8) ?? "[]"
}
}
Saving the toggle states is pretty simple
myObject.toggleStates = [firstAnswer, secondAnswer, thirdAnswer, fourthAnswer]
where myObject is an instance of the mentioned NSManagedObject subclass

Trying to set #published bool to true based on results from an API call

Hi first off I'm very new to swift and programing (coming from design field).
I'm trying to update doesNotificationsExist based on posts.count
I'm getting true inside the Api().getPosts {}
Where I print the following:
print("Api().getPosts")
print(doesNotificationExist)
but outside (in the loadData() {}) I still get false and not the #Publihed var doesNotificationExist:Bool = false doesn't update.
Please help me out, I would really appreciate some guidance to what I'm doing wrong and what I need to do.
Here is my code:
import SwiftUI
import Combine
public class DataStore: ObservableObject {
#Published var posts: [Post] = []
#Published var doesNotificationExist:Bool = false
init() {
loadData()
startApiWatch()
}
func loadData() {
Api().getPosts { [self] (posts) in
self.posts = posts
if posts.count >= 1 {
doesNotificationExist = true
}
else {
doesNotificationExist = false
}
print("Api().getPosts")
print(doesNotificationExist)
}
print("loadData")
print(doesNotificationExist)
}
func startApiWatch() {
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {_ in
self.loadData()
}
}
View where I'm trying to set an image based on store.doesNotificationsExist
StatusBarController:
import AppKit
import SwiftUI
class StatusBarController {
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
#ObservedObject var store = DataStore()
init(_ popover: NSPopover)
{
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = statusBar.statusItem(withLength: 28.0)
statusItem.button?.action = #selector(togglePopover(sender:))
statusItem.button?.target = self
if let statusBarButton = statusItem.button {
let itemImage = NSImage(named: store.doesNotificationExist ? "StatusItemImageNotification" : "StatusItemImage")
statusBarButton.image = itemImage
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
}
}
`Other none relevant code for the question`
}
It’s a closure and hopefully the #escaping one. #escaping is used to inform callers of a function that takes a closure that the closure might be stored or otherwise outlive the scope of the receiving function. So, your outside print statement will be called first with bool value false, and once timer is completed closure will be called changing your Bool value to true.
Check code below -:
import SwiftUI
public class Model: ObservableObject {
//#Published var posts: [Post] = []
#Published var doesNotificationExist:Bool = false
init() {
loadData()
// startApiWatch()
}
func loadData() {
getPost { [weak self] (posts) in
//self.posts = posts
if posts >= 1 {
self?.doesNotificationExist = true
}
else {
self?.doesNotificationExist = false
}
print("Api().getPosts")
print(self?.doesNotificationExist)
}
print("loadData")
print(doesNotificationExist)
}
func getPost(completion:#escaping (Int) -> ()){
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) {_ in
completion(5)
}
}
}
struct Test1:View {
#ObservedObject var test = Model()
var body: some View{
Text("\(test.doesNotificationExist.description)")
}
}

SwiftUI: Custom UITextView UIViewRepresentable requires double tap on the first action to work, works fine with single taps thereafter

I've built this small demo view where I have two NoteRows and my goal is to be able to press the return key to create a new row and for it to become the first responder. This works, however, the first time around, the new row is created but it doesn't become the first responder. Subsequent taps of the return key both create the row and become first responder.
Any ideas what's going wrong here? Thanks!
import SwiftUI
import Combine
struct FirstResponderDemo: View {
#State private var rows: [NoteRow] = [
.init(parentNoteId: "1", text: "foo"),
.init(parentNoteId: "1", text: "bar"),
]
#State private var activeRowId: String?
var body: some View {
ScrollViewReader { proxy in
ScrollView {
VStack {
ForEach(rows, id: \.id) { row in
ResponderTextView(
row: row,
text: $login,
activeRowId: $activeRowId,
returnPressed: returnPressed
)
.frame(width: 300, height: 44)
}
}.padding(.horizontal, 12)
}
}
.onAppear {
self.activeRowId = rows[0].id
}
}
private func returnPressed() {
guard let id = activeRowId else { return }
let newRow = NoteRow(parentNoteId: "1", text: "")
print("new row id", newRow.id)
if let index = rows.firstIndex(where: { $0.id == id }) {
rows.insert(newRow, at: index + 1)
activeRowId = newRow.id
}
}
}
struct FirstResponderDemo_Previews: PreviewProvider {
static var previews: some View {
FirstResponderDemo()
}
}
struct ResponderView<View: UIView>: UIViewRepresentable {
let row: NoteRow
#Binding var activeRowId: String?
var configuration = { (view: View) in }
func makeUIView(context: Context) -> View { View() }
func makeCoordinator() -> Coordinator {
Coordinator(row: row, activeRowId: $activeRowId)
}
func updateUIView(_ uiView: View, context: Context) {
context.coordinator.view = uiView
// print(activeRowId)
_ = activeRowId == row.id ? uiView.becomeFirstResponder() : uiView.resignFirstResponder()
configuration(uiView)
}
}
// MARK: - Coordinator
extension ResponderView {
final class Coordinator {
#Binding private var activeRowId: String?
private var anyCancellable: AnyCancellable?
fileprivate weak var view: UIView?
init(row: NoteRow, activeRowId: Binding<String?>) {
_activeRowId = activeRowId
self.anyCancellable = Publishers.keyboardHeight.sink(receiveValue: { [weak self] keyboardHeight in
guard let view = self?.view, let self = self else { return }
DispatchQueue.main.async {
if view.isFirstResponder {
self.activeRowId = row.id
print("active row id is:", self.activeRowId)
}
}
})
}
}
}
// MARK: - keyboardHeight
extension Publishers {
static var keyboardHeight: AnyPublisher<CGFloat, Never> {
let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
.map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 }
let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
.map { _ in CGFloat(0) }
return MergeMany(willShow, willHide)
.eraseToAnyPublisher()
}
}
struct ResponderView_Previews: PreviewProvider {
static var previews: some View {
ResponderView<UITextView>.init(row: .init(parentNoteId: "1", text: "Hello world"), activeRowId: .constant(nil)) { _ in
}.previewLayout(.fixed(width: 300, height: 40))
}
}
struct ResponderTextView: View {
let row: NoteRow
#State var text: String
#Binding var activeRowId: String?
private var textViewDelegate: TextViewDelegate
init(row: NoteRow, text: Binding<String>, activeRowId: Binding<String?>, returnPressed: #escaping () -> Void) {
self.row = row
self._text = State(initialValue: row.text)
self._activeRowId = activeRowId
self.textViewDelegate = TextViewDelegate(text: text, returnPressed: returnPressed)
}
var body: some View {
ResponderView<UITextView>(row: row, activeRowId: $activeRowId) {
$0.text = self.text
$0.delegate = self.textViewDelegate
}
}
}
// MARK: - TextFieldDelegate
private extension ResponderTextView {
final class TextViewDelegate: NSObject, UITextViewDelegate {
#Binding private(set) var text: String
let returnPressed: () -> Void
init(text: Binding<String>, returnPressed: #escaping () -> Void) {
_text = text
self.returnPressed = returnPressed
}
func textViewDidChange(_ textView: UITextView) {
DispatchQueue.main.async {
self.text = textView.text
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
returnPressed()
return false
}
return true
}
}
}
And the definition of NoteRow:
final class NoteRow: ObservableObject, Identifiable {
let id: String = UUID().uuidString
let parentNoteId: String
let text: String
init(parentNoteId: String, text: String) {
self.parentNoteId = parentNoteId
self.text = text
}
}
extension NoteRow: Equatable {
static func == (lhs: NoteRow, rhs: NoteRow) -> Bool {
lhs.id == rhs.id &&
lhs.parentNoteId == rhs.parentNoteId
}
}
Edit: Debugging this more
active row id is: Optional("71D8839A-D046-4DC5-8E02-F124779309E6") // first default row
active row id is: Optional("5937B1D0-CBB0-4BE4-A235-4D57835D7B0F") // second default row
// I hit return key:
new row id F640D1F9-0708-4099-BDA4-2682AF82E3BD
active row id is: Optional("5937B1D0-CBB0-4BE4-A235-4D57835D7B0F") // ID for 2nd row is set as active for some reason
// After that, new row id and active row ID follow the expected path:
new row id 9FDEB548-E19F-4572-BAD3-00E6CBB951D1
active row id is: Optional("9FDEB548-E19F-4572-BAD3-00E6CBB951D1")
new row id 4B5C1AA3-15A1-4449-B1A2-9D834013496A
active row id is: Optional("4B5C1AA3-15A1-4449-B1A2-9D834013496A")
new row id 22A61BE8-1BAD-4209-B46B-15666FF82D9B
active row id is: Optional("22A61BE8-1BAD-4209-B46B-15666FF82D9B")
new row id 95DD6B33-4421-4A32-8478-DCCBCBB1824E
active row id is: Optional("95DD6B33-4421-4A32-8478-DCCBCBB1824E")
Ok, I did not get idea of this code, but the below fixes case in question (tested with Xcode 12b)
if (text == "\n") {
DispatchQueue.main.async { // << defer to next event !!
self.returnPressed()
}
return false
}
return true
}

Property observers Swift 4

struct Kitchen // Coordinator
{
var foodItems = [FoodItem] () // Collection of FoodItem objects.
var wastedItems = [WastedItem]() // Collection of WastedItem
var totalSpend = Double()
var currentSpend = Double()
var weeklyHouseholdFoodBudget = Double()
var wastageCost = Double()
init(aTotal:Double,aCurrent:Double,aBudget:Double,aWastage:Double)
{
self.totalSpend = aTotal
self.currentSpend = aCurrent
self.weeklyHouseholdFoodBudget = aBudget
self.wastageCost = aWastage
}
struct FoodItem : Equatable
{
var itemName = String()
var itemQuantity = Double()
var dateOfUse = String()
var unitOfMeasurement = String()
init(aName:String,aQuantity:Double,aDateOfUse:String,aUnit:String)
{
self.itemName = aName
self.itemQuantity = aQuantity
self.dateOfUse = aDateOfUse
self.unitOfMeasurement = aUnit
}
mutating func listFoodItems()
{
for item in foodItems
{
print("Item Name:", item.getName(),",",
"Qunatity:",item.getItemQuantity(),",",
"Unit:",item.getUnitOfMeasurement(),",",
"Date of use:",item.getDateOfUse())
}
}
mutating func removeFoodItem(aFood:FoodItem)
{
if let anIndex = foodItems.index(of: aFood)
{
foodItems.remove(at: anIndex)
print(aFood.getName(),"Has been removed")
}
}
mutating func useFood(aFoodItem:inout FoodItem,inputQty:Double)
{
if (aFoodItem.getItemQuantity()) - (inputQty) <= 0
{
self.removeFoodItem(aFood: aFoodItem)
}
else
{
aFoodItem.useFoodItem(aQty: inputQty)
}
}
***Updated****
The issue I am having is when I use a func listFoodItems() the updated attribute itemQuantity does not change. I would like to know how to update the collection so when I call the func listFoodItems() it displays value changes.
The removal is ok, when the func runs the collection removes the object.
The issue must be because I am using for item in foodItems to display, I need to reload it with updated values before I do this?
Thank you for any assistance.
Property observers are used on properties.
The simplest example of a property observer.
class Foo {
var bar: String = "Some String" {
didSet {
print("did set value", bar)
}
}
}
Now if you want to display some changes, you will need your own code in didSet method in order to do that. For example call reloadData().

How to pass a reference to a Boolean rather than its value?

I want to use booleans as state storage.
In order to do that, I need to be able to change their state from difference places in the project.
To do that, I need somewhere to store them, and a way to pass a reference to them.
I've tried storing them as static variables in a GameManager, but passing references to these only seems to pass the value of true of false, not a reference.
How do I achieve this goal of having a passable boolean reference I can change the state of it from any part of the project?
UPDATE:
This can't be the best way to do this, but this achieves the goal of having a bunch of state booleans that I can use around the game world:
class GameManager {
static let sharedInstance = GameManager()
var previewAudioIsON: Bool = false
var previewVisuaIsOn: Bool = false
var timerDisplayIsOn: Bool = false
var quickStartIsOn: Bool = false
func touchedPreviewAudioButton() -> Bool {
if previewAudioIsON { previewAudioIsON = false}
else { previewAudioIsON = true }
return previewAudioIsON
}
func touchedPreviewVisualButton() -> Bool {
if previewVisuaIsOn { previewVisuaIsOn = false }
else { previewVisuaIsOn = true }
return previewVisuaIsOn
}
func touchedTimeDisplayButton() -> Bool {
if timerDisplayIsOn { timerDisplayIsOn = false }
else { timerDisplayIsOn = true }
return timerDisplayIsOn
}
func touchedQuickStartButton() -> Bool {
if quickStartIsOn { quickStartIsOn = false }
else { quickStartIsOn = true }
return quickStartIsOn
}
}
I gave you partially wrong information the other day ( I was having a brain fart), and need to apologize for that. I had overlooked something in my testing...
Here is what you need if you don't want to make the RefBool instances as I suggested (requires more legwork, not recommended):
/// Mutates a boolean:
func toggle(_ boolean: inout Bool) -> Bool {
boolean ? (boolean = false) : (boolean = true)
return boolean
}
/// Static state manager for Booleans
struct IsOn {
private static var
_previewAudio = false,
_previewVisual = false,
_timerDisplal = false,
_quickStart = false
enum State { case toggle, get }
static func previewAudio(_ toggleVal: State = .get) -> Bool {
if toggleVal == .toggle { toggle(&_previewAudio) }; return _previewAudio
}
// ... others
}
Testing:
let referenceToPA = IsOn.previewAudio
print ( IsOn.previewAudio() ) // False (default pram works)
print ( referenceToPA(.get) ) // False (can't use default pram)
referenceToPA(.toggle)
print ( IsOn.previewAudio() ) // True
print ( referenceToPA(.get) ) // True
IsOn.previewAudio(.toggle)
print ( IsOn.previewAudio() ) // False
print ( referenceToPA(.get) ) // False
But honestly, it would be easier to just do the RefBool from my other answer, then you wouldn't need the enum or the functions:
/// Holds a boolean in .val:
final class RefBool { var val: Bool; init(_ boolean: Bool) { val = boolean } }
/// Static state manager for Booleans
struct IsOn {
static var
previewAudio = RefBool(false),
previewVisual = RefBool(false),
timerDisplal = RefBool(false),
quickStart = RefBool(false)
}
Convenience Funcs (not necessary):
/// Mutates a boolean:
func toggle(_ boolean: inout Bool) -> Bool {
boolean ? (boolean = false) : (boolean = true)
return boolean
}
/// Mutates .val:
func toggle(_ refBool: RefBool) -> Bool {
refBool.val ? (refBool.val = false) : (refBool.val = true)
return refBool.val
}
Testing2:
let refToPA = IsOn.previewAudio
refToPA.val = true
print(refToPA.val) // true
print(IsOn.previewAudio.val) // true
toggle(&refToPA.val)
print(refToPA.val) // false
print(IsOn.previewAudio.val) // false
toggle(refToPA) // Using our fancy second toggle
print(refToPA.val) // true
print(IsOn.previewAudio.val) // true
try something like that:
Full example
import UIKit
enum ButtonType {
case PreviewAudio;
case PreviewVisua;
case TimerDisplay;
case QuickStart;
}
class SwitchProperty {
var type: ButtonType
var value: Bool
init (type: ButtonType) {
self.type = type
self.value = false
}
var description: String {
var result = "type = \(type)\n"
result += "value = \(value)"
return result
}
func switchValue() {
value.invertValue()
}
}
class GameManager {
static var previewAudioIsON = SwitchProperty(type: .PreviewAudio)
static var previewVisuaIsOn = SwitchProperty(type: .PreviewVisua)
static var timerDisplayIsOn = SwitchProperty(type: .TimerDisplay)
static var quickStartIsOn = SwitchProperty(type: .QuickStart)
}
class Button: UIButton {
var switchValue: SwitchProperty
init (type: ButtonType, frame: CGRect) {
switch type {
case .PreviewAudio:
switchValue = GameManager.previewAudioIsON
case .PreviewVisua:
switchValue = GameManager.previewVisuaIsOn
case .TimerDisplay:
switchValue = GameManager.timerDisplayIsOn
case .QuickStart:
switchValue = GameManager.quickStartIsOn
}
super.init(frame: frame)
addTarget(self, action: #selector(Button.buttonTouched), for: .touchUpInside)
}
func buttonTouched() {
switchValue.switchValue()
print(switchValue.description)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension Bool {
mutating func invertValue() {
self = !self
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addButton(type: .PreviewVisua, frame: CGRect(x: 40, y: 40, width: 200, height: 40));
addButton(type: .PreviewAudio, frame: CGRect(x: 40, y: 100, width: 200, height: 40));
}
func addButton(type: ButtonType, frame: CGRect) {
let button = Button(type: type, frame: frame);
button.setTitleColor(UIColor.blue, for: .normal)
button.setTitle("\(type)", for: .normal)
view.addSubview(button)
}
}
Properties accsess from another code
// change value
GameManager.previewAudioIsON.value = false
// check type
if (GameManager.previewAudioIsON.type == .PreviewAudio) {
print("previewAudioIsON")
}
Result