Swift: modifying data format through a button click - ios

My goal is to use a button (that contains multiple messages) to trigger a text (making a marker such as first click will be method 1, second click will be method 2) correspondingly added at the end of the my data (after joined(separator: "~")) so that it could help me to analyze which button was clicked when I look back at the data.
Currently, I have a struct that will output the data:
struct CaptureData {
var vertices: [SIMD3<Float>] //A vector of three scalar values. It will return a list of [SIMD3<Float>(x,y,z)]
var mode: Mode = .one
mutating func nextCase() { // the data method will be changed
mode = mode.next()
}
var verticesFormatted : String { //I formatted in such a way so that it can be read more clearly without SIMD3
let v = "<" + vertices.map{ "\($0.x):\($0.y):\($0.z)" }.joined(separator: "~") + "trial: \(mode.next().rawValue)"
return "\(v)"
}
}
Based on #Joshua suggestion
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self {
return self.nextCase
}
}
And the button is alternating the messages after each click,
var x = 0
var instance = CaptureData(vertices: [SIMD3<Float>])
// Next button for changing methods
#IBAction func ChangingTapped(_ btn: UIButton) {
if(x==0){
Textfield.text = "changing to driving"
}
else if(x==1){
Textfield.text = "changing to walking"
instance.nextCase()
}
else{
Textfield.text = "changing to cycling"
instance.nextCase()
}
x += 1
}
Updates: I am able to print one of the methods , .two (method two), after separator: "~". However, at the moment I am still not be able to click button to switch the case in the data.
The main problem is the initialization of variables. I am not able to define var instance = CaptureData(vertices: [SIMD3<Float>]) because it comes with error: Cannot convert value of type '[SIMD3<Float>].Type' to expected argument type '[SIMD3<Float>]'
I am sorry if my explanation is a bit messy here. I am trying to describe the problem I have here. Let me know if there is anything missing! Thank you so much in advance.

Enums is a data type that is more like a constant but much more readable.
An example will be passing in a status to a function.
enum Status {
case success
case failure
}
func updateStatus(_ status: Status) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(.success)
versus using an Int as a value.
func updateStatus(_ status: Int) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(1) // eventually you'll forget what this and you'll declare more of a global variable acting as constant, which technically what enums are for.
Enums in swift are a bit different though, much more powerful. More info about enums here
Back to the topic.
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self { // you don't need to update self here, remember self here is one of the items in the enum, i.e. one, so assigning one = two just doesn't work.
return self.nextCase
}
}
// The data struct
struct CaptureData {
var mode: Mode = .one
// we add a mutation function here so we can update the mode
mutating func nextCase() { // the data your concern about, that can actually mutate is the mode property inside CaptureData struct.
mode = mode.next()
}
}
So lets say somewhere in the app you can use it like this you initialised an instance of CaptureData:
var instance = CaptureData() // Don't forget it should be var and not let, as we are updating its property.
instance.nextCase() // get the next case, initially it was .one
print(instance.mode) // prints two
instance.nextCase() // from .two, changes to .three
print(instance.mode) // prints three
Hope this helps.

Related

How to pass data between controller, checker and back using callback closures Swift

I'm a newbie in Swift and this is my very first question I want to ask the community.
I have a viewcontroller with a textField, custom button and a label and also I have a separate checker to check whether the entered in textField word is correct or not. So when I push the button the checker is checking and changing the color of the label (green/ red / transparent if textfield.text is empty. How can I implement this using callback closure? Thank you so much in advance!
Here is my ViewController:
final class FeedViewController: UIViewController {
private let checkTextField: UITextField = {
let textField = MyCustomTextField(
font: UIFont.systemFont(ofSize: 16),
textColor: .black,
backgroundColor: .white,
placeholder: "Enter the word")
return textField
}()
private lazy var checkButton: UIButton = {
let button = MyCustomButton(
title: "Check the word",
titleColor: .white,
backgroundColor: .systemGray,
backgroundImage: nil) {
}
button.layer.cornerRadius = 6
button.clipsToBounds = true
return button
}()
private let colorLabel: UILabel = {
let label = UILabel()
label.alpha = 0
label.toAutoLayout()
return label
}()
private let checker: CheckTextField
init(checker: CheckTextField) {
self.checker = checker
super .init(nibName: nil, bundle: nil)
}
Here is my Checker:
class CheckTextField {
private let correctWord = "correct word"
func check( word: String) {
}
}
I have managed to complete this task with notifications, but closures for me are too much complicated :(
you can define your clousure in data sending class as follow
class DataSenderClass {
var dataPassClousure:((String)->Void)?
func sendHere(){
// i am invoking closure from here
dataPassClousure?("hi i am data")
}
}
here is dataPassClousure accepting string as argument(you can adjust according to your choice or can use object as well or can also pass multiple arguments to your closure) to be sent/passed to receiver class.
Just listen to fresh data in your data receiver class as follow
class DataReceiverClass {
let senderObject = DataSenderClass()
func receiveHere() {
//each time data change from sendHere function this closure invoke . perform your action
senderObject.dataPassClousure = {arug1 in
print("received data is \(arug1)")
}
}
}
The closure is actually a parameter of the method that receives and calls it, so if you want check(word:) to call some closure when it finishes checking, you'll need to add that as a parameter:
func check(word: String, closure: ()->Void)
If you want to pass a parameter to the closure, you'll need to specify that too:
func check(word: String, closure: (status: CheckResult)->Void)
I just made up CheckResult here; I just needed some type that you can use to convey the result of the check. It could be a an enumeration, for example:
enum CheckResult {
case ok;
case bad;
case empty;
}
Now, inside your check function, you can call the closure with the appropriate value, like: closure(.ok). To be more concrete, let's fill in your empty function:
class CheckTextField {
private let correctWord = "apple"
func check(word: String, closure: (_ status: CheckResult)->Void) {
if word.count == 0 {
closure(.empty)
} else if word == correctWord {
closure(.ok)
} else {
closure(.bad)
}
}
}
Then we can use it this way:
let checker = CheckTextField()
checker.check(word:"apple") { print($0) } // ok
checker.check(word:"pear") { print($0) } // bad
checker.check(word:"jackfruit") { print($0) } // bad
checker.check(word:"") { print($0) } // empty
You can replace the print($0) with code to set the label color or whatever else you might want to do. That's nice, because it keeps details about your view out of the CheckTextField class.

How to call a method once two variables have been set

I am using iOS Swift, and I am trying to understand how to execute a method once the value of two variables have been set up (non-null value) once the requests have finished.
After reading some documentation, I have found out some concepts which are interesting. The first one would be didSet, which works as an observer.
I could call the method using this method by simply using didSet if I would require just one variable
didSet
var myVar: String 0 {
didSet {
print("Hello World.")
}
}
Nevertheless, I also need to wait for the second one myVar2, so it would not work.
I have also found DispatchQueue, which I could use to wait a second before calling the method (the requests that I am using are pretty fast)
DispatchQueue
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
print("Hello world")
})
but I consider that this solution is not efficient.
Is there anyway to combine these two variables or requests in order to call a method once they have finishing setting the value?
Update
I have tried to replicate David s answer, which I believe is correct but I get the following error on each \.
Type of expression is ambiguous without more context
I copy here my current code
var propertiesSet: [KeyPath<SearchViewController, Car>:Bool] = [\SearchViewController.firstCar:false, \SearchViewController.secondCar:false] {
didSet {
if propertiesSet.allSatisfy({ $0.value }) {
// Conditions passed, execute your custom logic
print("All Set")
} else {
print("Not yet")
}
}
}
var firstCar: Car? {
didSet {
propertiesSet[\SearchViewController.firstCar] = true
}
}
var secondCar: Car? {
didSet {
propertiesSet[\SearchViewController.secondCar] = true
}
}
The variables are set individually, each one on its own request.
You could make your properties optional and check they both have values set before calling your function.
var varA: String? = nil {
didSet {
if varA != nil && varB != nil {
myFunc()
}
}
}
var varB: String? = nil {
didSet {
if varA != nil && varB != nil {
myFunc()
}
}
}
Or you can call your function on each didSet and use a guard condition at the start of your function to check that both of your properties have values, or bail out:
var varA: String? = nil {
didSet {
myFunc()
}
}
var varB: String? = nil {
didSet {
myFunc()
}
}
func myFunc() {
guard varA != nil && varB != nil else { return }
// your code
}
First, you should think very carefully about what your semantics are here. When you say "set," do you mean "assigned a value" or do you mean "assigned a non-nil value?" (I assume you mean the latter in this case.) You should ask yourself, what should happen if your method has already fired, and then another value is set? What if one of the properties has a value is set, then nil is set, then another value set? Should that fire the method 1, 2, or 3 times?
Whenever possible you should work to make these kinds of issues impossible by requiring that the values be set together, in an init rather than mutable properties, for example.
But obviously there are cases where this is necessary (UI is the most common).
If you're targeting iOS 13+, you should explore Combine for these kinds of problems. As one approach:
class Model: ObservableObject {
#Published var first: String?
#Published var second: String?
#Published var ready = false
private var observers: Set<AnyCancellable> = []
init() {
$first.combineLatest($second)
.map { $0 != nil && $1 != nil }
.assign(to: \.ready, on: self)
.store(in: &observers)
}
}
let model = Model()
var observers: Set<AnyCancellable> = []
model.$ready
.sink { if $0 { print("GO!") } }
.store(in: &observers)
model.first = "ready"
model.second = "set"
// prints "GO!"
Another approach is to separate the incidental state that includes optionals, from the actual object you're constructing, which does not.
// Possible parameters for Thing
struct Parameters {
var first: String?
var second: String?
}
// The thing you're actually constructing that requires all the parameters
struct Thing {
let first: String
let second: String
init?(parameters: Parameters) {
guard let first = parameters.first,
let second = parameters.second
else { return nil }
self.first = first
self.second = second
}
}
class TheUIElement {
// Any time the parameters change, try to make a Thing
var parameters: Parameters = Parameters() {
didSet {
thing = Thing(parameters: parameters)
}
}
// If you can make a Thing, then Go!
var thing: Thing? {
didSet {
if thing != nil { print("GO!") }
}
}
}
let element = TheUIElement()
element.parameters.first = "x"
element.parameters.second = "y"
// Prints "GO!"
You need to add a didSet to all variables that need to be set for your condition to pass. Also create a Dictionary containing KeyPaths to your variables that need to be set and a Bool representing whether they have been set already.
Then you can create a didSet on your Dictionary containing the "set-state" of your required variables and when all of their values are true meaning that all of them have been set, execute your code.
This solution scales well to any number of properties due to the use of a Dictionary rather than manually writing conditions like if aSet && bSet && cSet, which can get out of hand very easily.
class AllSet {
var propertiesSet: [KeyPath<AllSet, String>:Bool] = [\.myVar:false, \.myVar2:false] {
didSet {
if propertiesSet.allSatisfy({ $0.value }) {
// Conditions passed, execute your custom logic
print("All Set")
} else {
print("Not yet")
}
}
}
var myVar: String {
didSet {
propertiesSet[\.myVar] = true
}
}
var myVar2: String {
didSet {
propertiesSet[\.myVar2] = true
}
}
init(myVar: String, myVar2: String) {
self.myVar = myVar
self.myVar2 = myVar2
}
}
let all = AllSet(myVar: "1", myVar2: "2")
all.myVar = "2" // prints "Not yet"
all.myVar2 = "1" // prints "All set"

Observe values added to Set - Swift

Is it possible to observe values being added to a Set data-structure?
What i'm trying to achieve:
var storedStrings = Set<String>() {
didSet (value) {
// where value is a string that has been added to the Set
}
}
Example:
storedStrings.insert("hello")
didSet called, as a new value has been added.
storedString.insert("world")
didSet called again.
storedString.insert("hello")
didSet not called, as the set already contains the string "hello"
This can be a bit expensive, but you still can do something like:
var storedStrings = Set<String>() {
didSet {
if storedStrings != oldValue {
print("storedStrings has changed")
let added = storedStrings.subtracting(oldValue)
print("added values: \(added)")
let removed = oldValue.subtracting(storedStrings)
print("removed values: \(removed)")
}
}
}
The insert function returns a tuple with the definition: (inserted: Bool, memberAfterInsert: Element).
Therefore the check for a new unique element can be made on insertion instead of using didSet.
var storedStrings = Set<String>()
var insertionResult = storedStrings.insert("Hello")
if insertionResult.inserted {
print("Value inserted") // this is called
}
insertionResult = storedStrings.insert("Hello")
if insertionResult.inserted {
print("Value inserted") // this isn't called
}
You could implement your own inserter for your set, which could emulate the use of a property observer, making use of the fact that the insert method of Set returns a tuple whose first member is false in case the element to be inserted is already present.
func insert(Element)
Inserts the given element in the set if it is not already present.
From the language reference.
E.g.:
struct Foo {
private var storedStrings = Set<String>()
mutating func insertNewStoredString(_ newString: String) {
if storedStrings.insert(newString).0 {
print("Inserted '\(newString)' into storedStrings")
}
}
}
var foo = Foo()
foo.insertNewStoredString("hello") // Inserted 'hello' into storedStrings
foo.insertNewStoredString("hello")
foo.insertNewStoredString("world") // Inserted 'world' into storedStrings

Custom collection in swift: is it a right way?

I am learning swift. I would like to use a custom class to be loopable [able to a for...in loop] like Array. Below is the given sample code that so far, I have tried. The class in question is "GuestManager" which is holding a private collection of guests [objects of class Guest]
import Foundation
class Guest{
var guestId: String
var guestName: String
init(gId: String, name: String){
self.guestId = gId
self.guestName = name
}
}
class GuestManager: GeneratorType, SequenceType{
private var guests = [Guest]?()
private var nextIndex: Int
init(guests: [Guest]){
self.guests = guests
self.nextIndex = 0
}
func next() -> Guest? {
if self.nextIndex > (self.guests?.count)! - 1 {
self.nextIndex = 0
return nil
}
let currentGuest = self.guests![self.nextIndex]
self.nextIndex += 1
return currentGuest
}
subscript(aGuestId gID: String) -> Guest{
return (self.guests?.filter({$0.guestId == gID}).first)!
}
}
I do not want to create separate classes that are conforming to GeneratorType & SequenceType protocols. Instead I have created a single class that is conforming to both protocols.
Below are some of my questions:
I would like to know if this a correct way to have a custom collection type ?
Can we use subscript as a way to perform a search based on a property for example "subscript(aGuestId gID: String)" in the sample code above ?
It is clear from the code for next() function implementation in above sample code that is resetting the "nextIndex" when the iteration reached at the end. How one will handle the situation wherein we use a break statement inside the for...in loop as below:
for aGuest in guestManager{//guestManager an instance of GuestManager class instantiated with several guest objects
print(aGuest.guestName)
}
for aG in guestManager{
print(aG.guestId)
break
}
In the 2nd for loop the code break out after getting the first Element [Guest object in this case]. The subsequent for loop will start at index 1 in the collection and not at 0. Is there anyway to handle this break situation so that for each subsequent for looping the index is always set to 0?
Thanks
Edit: It seems the "nextIndex" reset issue can be fixed with below code [added inside GuestManager class] for generate() method implementation
func generate() -> Self {
self.nextIndex = 0
return self
}
You should not store the nextIndex inside the class. You can use a local variable in the generate method and then let that variable be captured by the closure you pass to the generator you create in that method. That’s all you need to adopt SequenceType:
class GuestManager: SequenceType{
private var guests: [Guest]
init(guests: [Guest]) {
self.guests = guests
}
func generate() -> AnyGenerator<Guest> {
var nextIndex = 0
return AnyGenerator {
guard nextIndex < self.guests.endIndex else {
return nil
}
let next = self.guests[nextIndex]
nextIndex += 1
return next
}
}
}
For subscripting, you should adopt Indexable. Actually, the easiest way to fulfill all your requirements is to pass as much of the logic for SequenceType, Indexable, and eventually (if you want to support it) CollectionType, to your array, which already has these capabilities. I would write it like this:
class GuestManager {
private var guests: [Guest]
init(guests: [Guest]){
self.guests = guests
}
}
extension GuestManager: SequenceType {
typealias Generator = IndexingGenerator<GuestManager>
func generate() -> Generator {
return IndexingGenerator(self)
}
}
extension GuestManager: Indexable {
var startIndex: Int {
return guests.startIndex
}
var endIndex: Int {
return guests.endIndex
}
subscript(position: Int) -> Guest {
return guests[position]
}
}
Some more observations:
Your guests property should not be an optional. It makes the code more complicated, with no benefits. I changed it accordingly in my code.
Your Guest class should probably be a value type (a struct). GuestManager is also a good candidate for a struct unless you require the reference semantics of a class (all collection types in the standard library are structs).
I think the subscripting approach you're trying here is kind of convoluted. Personally, I would use a function to do this for the sake of clarity.
guestManager[aGuestId: guestId]
guestManager.guestWithID(guestId)
So stylistically I would probably land on something like this
import Foundation
class Guest{
var guestId: String
var guestName: String
init(guestId: String, guestName: String){
self.guestId = guestId
self.guestName = guestName
}
}
class GuestManager: GeneratorType, SequenceType{
private var guests: [Guest]
private var nextIndex = 0
init(guests: [Guest]){
self.guests = guests
}
func next() -> Guest? {
guard guests.count < nextIndex else {
nextIndex = 0
return nil
}
let currentGuest = guests[nextIndex]
nextIndex += 1
return currentGuest
}
func guestWithID(id: String) -> Guest? {
return guests.filter{$0.guestId == id}.first ?? nil
}
}

Can i have 2 types on parameter in swift?

so i've created function and it does something, but i want it to do something else if the parameter type is different, for example:
func (parameter: unknownType){
if(typeof parameter == Int){
//do this
}else if(typeof parameter == String){
//do that
}
}
i've done this in javascript or other programming languages, but i don't know how to do this in swift
i've created function which takes 1 argument UITextField and centers it using constraints
now i want to center my button, but since button is not UITextField type it does not work, so is there a way i can tell function to do the same on UIButton??
Use Overload:
class Example
{
func method(a : String) -> NSString {
return a;
}
func method(a : UInt) -> NSString {
return "{\(a)}"
}
}
Example().method("Foo") // "Foo"
Example().method(123) // "{123}"
The equivalent of the Javascript code would be:
func doSomething(parameter: Any?) {
if let intValue = parameter as? Int {
// do something with the int
} else if let stringValue = parameter as? String {
// do something with the string
}
}
But be warned, this approach makes you loose the type safety which is one of most useful feature of Swift.
A better approach would be to declare a protocol that is implemented by all types that you want to allow to be passed to doSomething:
protocol MyProtocol {
func doSomething()
}
extension Int: MyProtocol {
func doSomething() {
print("I am an int")
}
}
extension String: MyProtocol {
func doSomething() {
print("I am a string")
}
}
func doSomething(parameter: MyProtocol) {
parameter.doSomething()
}
doSomething(1) // will print "I am an int"
doSomething("a") // will print "I am a string"
doSomething(14.0) // compiler error as Double does not conform to MyProtocol
It can
Sample code:
func temFunc(obj:AnyObject){
if let intValue = obj as? Int{
print(intValue)
}else if let str = obj as? String{
print(str)
}
}
You can make use of Any and downcasting:
func foo(bar: Any){
switch(bar) {
case let a as String:
/* do something with String instance 'a' */
print("The bar is a String, bar = " + a)
case let a as Int:
/* do something with Int instance 'a' */
print("The bar is an Int, bar = \(a)")
case _ : print("The bar is neither an Int nor a String, bar = \(bar)")
}
}
/* Example */
var myString = "Hello"
var myInt = 1
var myDouble = 1.5
foo(myString) // The bar is a String, bar = Hello
foo(myInt) // The bar is an Int, bar = 1
foo(myDouble) // The bar is neither an Int nor a String, bar = 1.5
Check this solution:
https://stackoverflow.com/a/25528882/256738
You can pass an object AnyObject and check the class in order to know what kind of object it is.
UPDATE
Good point #Vojtech Vrbka
Here an example:
let x : AnyObject = "abc"
switch x {
case is String: println("I'm a string")
case is Array: println("I'm an Array")
// Other cases
default: println("Unknown")
}

Resources