How to determine a struct's return value based on UI Objects - swift

I currently have a struct in which my ViewController references to return a random value from a group of arrays (aList, bList, cList, & dList). I ideally would like to be have aList always enabled while being able to toggle bList, cList, & dList on/off.
I have all this in a struct (shown below) but I'm very new to Swift and am learning that structs are "value types" while the ViewController is a "reference type" and apparently UI objects can only be referenced from reference types?
import GameKit
import Foundation
struct PromptProvider {
var aListIndex = 0
var bListIndex = 0
var cListIndex = 0
var dListIndex = 0
var includeB = false
var includeC = false
var includeD = false
var aList = [
“A1”,
“A2”,
“A3”,
“A4”]
var bList = [
“B1”,
“B2”,
“B3”,
“B4”]
var cList = [
“C1”,
“C2”,
“C3”,
“C4”]
var dList = [
“D1”,
“D2”,
“D3”,
“D4”]
init() {
self.restart()
}
mutating func restart() {
if includeB == true {self.aList.append(contentsOf: bList)}
if includeC == true {self.aList.append(contentsOf: cList)}
if includeD == true {self.aList.append(contentsOf: dList)}
self.aList.shuffle()
self.aListIndex = 0
}
mutating func randomPrompt() -> String {
if aListIndex == aList.count {
return "Out of String Values"
}
else {
defer {aListIndex += 1}
return aList[aListIndex]
}
}
}
I think I'm treating the struct as a reference type here but am unable to connect a UI object to the structs code.
Any help/guidance on how to pivot my strategy with this would be greatly appreciated. Thank you

As long as you have a single instance of PromptProvider you can make changes to that instance at will. If you need multiple references to a PromptProvider then you will need to make PromptProvider a class.
I mentioned that you had a bug in your restart method. You were appending data to your aList array without clearing out the previous appended data. What you need is a master list to contain your aList as well as the contents of your other lists so you can reset it as needed.
If I were you, I'd implement your PromptProvider struct like this:
struct PromptProvider {
let aList = [
"A1",
"A2",
"A3",
"A4"]
let bList = [
"B1",
"B2",
"B3",
"B4"]
let cList = [
"C1",
"C2",
"C3",
"C4"]
let dList = [
"D1",
"D2",
"D3",
"D4"]
var includeB = false {
didSet { restart() }
}
var includeC = false {
didSet { restart() }
}
var includeD = false {
didSet { restart() }
}
var masterList: [String] = []
init() {
self.restart()
}
mutating func restart() {
masterList.removeAll()
masterList.append(contentsOf: aList)
if includeB == true {masterList.append(contentsOf: bList)}
if includeC == true {masterList.append(contentsOf: cList)}
if includeD == true {masterList.append(contentsOf: dList)}
masterList.shuffle()
}
mutating func randomPrompt() -> String? {
return masterList.popLast()
}
}
And then in your view controller you can declare and use your PromptProvider like so:
class ViewController: UIViewController {
var promptProvider = PromptProvider()
#IBAction func onPrintRandomPrompt(_ sender: UIButton) {
if let prompt = promptProvider.randomPrompt() {
print(prompt)
} else {
print("Out of String Values")
promptProvider.restart()
}
}
}
If you want to change the contents of the provider all you have to do is toggle its flags and that will reset the contents of the master list.
extension ViewController {
#IBAction func onIncludeB(_ sender: UIButton) {
promptProvider.includeB.toggle()
sender.setTitleColor(promptProvider.includeB ? .green : .red, for: .normal)
}
#IBAction func onIncludeC(_ sender: UIButton) {
promptProvider.includeC.toggle()
sender.setTitleColor(promptProvider.includeC ? .green : .red, for: .normal)
}
#IBAction func onIncludeD(_ sender: UIButton) {
promptProvider.includeD.toggle()
sender.setTitleColor(promptProvider.includeD ? .green : .red, for: .normal)
}
}

Related

How do I modify function to be used in MVP architecture?

I have the function below. It works properly.
When a user types any character it validates the user input and hides some imageView based on the input.
#IBAction func onEmailValueChanged(_ sender: UITextField) {
let hasMinimumLength = TextValidationHelper.validateHasMinimumLength(password: sender.text!)
passLengthCheckmarkImageView.isHidden = hasMinimumLength ? false : true
let hasCapitalLetter = TextValidationHelper.validateHasCapitalLetter(password: sender.text!)
passHasUppercaseCheckmarkImageView.isHidden = hasCapitalLetter ? false : true
let hasNumber = TextValidationHelper.validateHasNumber(password: sender.text!)
passHasNumberCheckmarkImageView.isHidden = hasNumber ? false : true
let hasSpecialCharacter = TextValidationHelper.validateHasSpecialCharacter(password: sender.text!)
passHasSymbolCheckmarkImageView.isHidden = hasSpecialCharacter ? false : true
resetButton.isHidden = hasMinimumLength && hasCapitalLetter && hasNumber && hasSpecialCharacter ? false : true
}
But now I want to apply an MVP model on this function to remove the function from the ViewController file.
How can I do that?
Do I need to publish more code to make it possible to create an answer for this question?
It is not a good practice to use any architectural pattern only for method. So assuming you are having a complete app with many classes or files.
An important thing is that it is not fixed/compulsory to use any specific pattern. It actually depends on the code, sometimes you end up writing much code just to handle a method. So try to think the optimal approach to make the code more testable and scalable.
But for your reference, you can check the following code:
On ViewController:
lazy var presenter:Presenter = Presenter(view:self)
#IBAction func onEmailValueChanged(_ sender: UITextField) {
presenter.validateHasMinimumLength(password: sender.text!)
presenter.validateHasCapitalLetter(password: sender.text!)
presenter.validateHasNumber(password: sender.text!)
presenter.validateHasSpecialCharacter(password: sender.text!)
}
//Adopting ViewController:PrensenterViewProtocol on ViewController
extension ViewController:PrensenterViewProtocol {
func updateLengthCheckmarkImageView(isHidden:Bool) {
passLengthCheckmarkImageView.isHidden = isHidden
}
func updateUpperCaseCheckmarkImageView(isHidden:Bool) {
passHasUppercaseCheckmarkImageView.isHidden = isHidden
}
func updateNumberCheckmarkImageView(isHidden:Bool) {
passHasNumberCheckmarkImageView.isHidden = isHidden
}
func updateSymbolCheckmarkImageView(isHidden:Bool) {
passHasSymbolCheckmarkImageView.isHidden = isHidden
}
func updateResetButton(isHidden:Bool) {
resetButton.isHidden = isHidden
}
}
PresenterView protocol as:
protocol PrensenterViewProtocol:NSObjectProtocol {
func updateLengthCheckmarkImageView(isHidden:Bool)
func updateUpperCaseCheckmarkImageView(isHidden:Bool)
func updateNumberCheckmarkImageView(isHidden:Bool)
func updateSymbolCheckmarkImageView(isHidden:Bool)
func updateResetButton(isHidden:Bool)
}
Presenter as:
class Presenter {
weak var view:PrensenterViewProtocol!
private var hasMinimumLength:Bool = false
private var hasCapitalLetter:Bool = false
private var hasNumber:Bool = false
private var hasSpecialCharacter:Bool = false
init(view:PrensenterViewProtocol) {
self.view = view
}
func validateHasMinimumLength(password:String?) {
hasMinimumLength = TextValidationHelper.validateHasMinimumLength(password: password)
self.view.updateLengthCheckmarkImageView(isHidden: hasMinimumLength)
checkAllValidations()
}
func validateHasCapitalLetter(password:String?) {
hasCapitalLetter = TextValidationHelper.validateHasCapitalLetter(password: password)
self.view.updateUpperCaseCheckmarkImageView(isHidden:hasCapitalLetter )
checkAllValidations()
}
func validateHasNumber(password:String?) {
hasNumber = TextValidationHelper.validateHasNumber(password: password)
self.view.updateNumberCheckmarkImageView(isHidden: hasNumber)
checkAllValidations()
}
func validateHasSpecialCharacter(password:String?) {
hasSpecialCharacter = TextValidationHelper.validateHasSpecialCharacter(password: password)
self.view.updateSymbolCheckmarkImageView(isHidden: hasSpecialCharacter)
checkAllValidations()
}
func checkAllValidations() {
let areAllValid:Bool = hasMinimumLength && hasCapitalLetter && hasNumber && hasSpecialCharacter ? false : true
self.view.updateResetButton(isHidden: areAllValid)
}
}

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 - Is it possible to change an ActionSheet button text after it is displayed?

I would like to show an ActionSheet containing InApp purchase objects the user can purchase.
But I want that sheet to contain the prices of such objects, like:
Object 1 ($1.99)
Object 2 ($2.99)
...
but the price is asynchronous, cause it has to be retrieved from the store.
So, I thought about doing this:
struct Package {
enum Packtype:String {
typealias RawValue = String
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
var productID:String = ""
#State var namePriceString:String = ""
init(productID:String) {
self.productID = productID
}
}
then, when I create the action sheet button I do this:
var obj1 = Package(productID: Package.Packtype.obj1.rawValue)
var obj2 = Package(productID: Package.Packtype.obj2.rawValue)
self.getPrices(packages:[obj1, obj2])
let obj1Button = ActionSheet.Button.default(Text(obj1.$namePriceString)) {
// do something with obj1
}
let obj2Button = ActionSheet.Button.default(Text(obj2.$namePriceString)) {
// do something with obj1
}
// build the actionsheet
later in the code:
func getPrices(packages:[Package]) {
let productIDS = Set(packages.map {$0.productID})
SwiftyStoreKit.retrieveProductsInfo(productIDS) { (answer) in
if answer.invalidProductIDs.first != nil { return }
let results = answer.retrievedProducts
if results.count == 0 { return }
for result in answer {
if let package = packages.filter({ ($0.productID == result.productIdentifier) }).first {
package.namePriceString = result.localizedTitle + "(" + "\(result.localizedPrice!)" + ")"
}
}
}
}
I have an error pointing to Text on the button creation lines saying
Initializer 'init(_:)' requires that 'Binding' conform to
'StringProtocol'
In a nutshell I need this:
I display the actionsheet. Its buttons contain no price.
I retrieve the prices
Actionsheet buttons are updated with the prices.
A possible solution is to return prices in a completion handler and only then display the action sheet:
struct ContentView: View {
#State var showActionSheet = false
#State var localizedPrices = [Package: String]()
var body: some View {
Button("Get prices") {
getPrices(packages: Package.allCases, completion: {
localizedPrices = $0
showActionSheet = true
})
}
.actionSheet(isPresented: $showActionSheet) {
let buttons = localizedPrices.map { package, localizedPrice in
ActionSheet.Button.default(Text(localizedPrice), action: { buy(package: package) })
}
return ActionSheet(title: Text("Title"), message: Text("Message"), buttons: buttons + [.cancel()])
}
}
}
func getPrices(packages: [Package], completion: #escaping ([Package: String]) -> Void) {
// simulates an asynchronous task, should be replaced with the actual implementation
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let localizedPrices = Dictionary(uniqueKeysWithValues: packages.map { ($0, "\(Int.random(in: 1 ..< 100))") })
completion(localizedPrices)
}
}
func buy(package: Package) {
print("Buying \(package.rawValue)")
}
enum Package: String, CaseIterable {
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
This can be further tuned with loading animations etc...

Unresolved Identifier 'count'

Here is the error that I am seeing.
The "cardButton" is responsible for showing the next question. This is a small card app game that I am trying to make and as you can see from the image this is where I am having the issue with the code.
Here is the code:
import UIKit
class MultipleViewController: UIViewController {
#IBOutlet weak var questionLabel2: UILabel!
#IBOutlet var answerButtons: [UIButton]!
#IBOutlet weak var cardButton: UIButton!
#IBAction func cardButtonHandle(_ sender: Any) {
cardButton.isEnabled = true
if questionIdx < count(mcArray) - 1 { // There are still more questions
questionIdx += 1 //
} else {
questionIdx = 0
}
nextQuestion()
}
#IBAction func answerButtonHandle(_ sender: UIButton) {
if sender.titleLabel?.text == correctAnswer{
sender.backgroundColor = UIColor.green
print("Correct!")
} else {
sender.backgroundColor = UIColor.red
print("Wrong Answer")
}
for button in answerButtons{
button.isEnabled = false
if button.titleLabel?.text == correctAnswer {
button.backgroundColor = UIColor.green
}
}
cardButton.isEnabled = true // next question
}
var correctAnswer: String? // correct answers
var answers = [String]() // answers
var question : String? // Questions
var questionIdx = 0 // not sure what this is ?
override func viewDidLoad() {
super.viewDidLoad()
// titleForButtons() // call buttons as soon its loaded..
cardButton.isEnabled = false
nextQuestion()
}
func nextQuestion (){
let currentQuestion = mcArray![questionIdx]
answers = currentQuestion["Answers"] as! [String]
correctAnswer = currentQuestion["CorrectAnswer"] as? String
question = currentQuestion["Question"] as? String
titleForButtons ()
}
func titleForButtons (){
for (idx,button) in answerButtons .enumerated() {
button.titleLabel?.lineBreakMode = .byWordWrapping
button.setTitle(answers[idx],for:.normal)
button.isEnabled = true
}
questionLabel2.text = question
}
}
The following should work, you did not have the correct syntax for the length of the array. Note that if you have not initialized your questions array, this would cause a crash. Therefore you might want to add a guard into your code. Perhaps use the following
#IBAction func cardButtonHandle(_ sender: Any) {
cardButton.isEnabled = true
if questionIdx < (mcArray!.count) - 1 { // There are still more questions
questionIdx += 1 //
} else {
questionIdx = 0
}
nextQuestion()
}

Call #IBAction function without pressing the button

How i can call button1() func from calc() with original sender(like if i tap this button)? Call func with nil is bad soulution, because original sender is lost and i cant change button image through sender?.image = UIImage(named: "").
All answers in others topics offer use button.sendActions(for: .touchUpInside). But this solution isn't working.
var var1 = true
var var2 = 1
#IBAction func button1(_ sender: UIBarButtonItem?) {
if var2 == 1 {
sender?.image = UIImage(named: "1")
} else {
sender?.image = UIImage(named: "2")
}
}
func calc() {
if var1 {
button1(nil)
}
}
Make outlet of button and pass the reference as sender
#IBOutlet var outletButton: UIButton!
func calc() {
if var1 {
button1(outletButton)
}
}
var var1 = true
var var2 = 1
#IBAction func button1(_ sender: UIBarButtonItem?) {
button1Action()
}
func button1Action() {
if var2 == 1 {
sender?.image = UIImage(named: "1")
} else {
sender?.image = UIImage(named: "2")
}
}
func calc() {
if var1 {
button1Action()
}
}
you can callself.perform(Selector, with: self)