Loops for Dictionary key items - swift

How can I fix the code so the loop would ask the question for each of the items in the dictionary?
*Edited the code to make what I am trying to do more clear. I want the code to stop and wait for the input BEFORE moving on to the next item in the dictionary.
import Foundation
import UIKit
var squad = [1:"Arsh", 2:"Arbab", 3:"Ayush", 4:"KC", 5:"Jaski", 6:"Gunny", 7:"Harsh", 8:"Nagib", 9:"Rithin", 10:"Gursh", 11:"Sonny"]
let coming = "Yes"
let notComing = "No"
var attendees = [String]()
for ( key , person) in squad {
// key == 1
// key = counter
// repeat {
print("Are you attending \(person)?")
let response = readLine()
print(response as Any)
if response == coming {
attendees.append(person)
}
else if response == notComing {
print("Sorry to hear that")
}
else {
print("Invalid Input")
}
// } while key <= 11
// key += 1
}
print(attendees)
print(attendees.count)

To experiment keyboard input features like readLine(), create a project by choosing from Xcode menu :
File > New > Project > macOS > Command Line Tool
and run it in Xcode, the Debugging Console pane will work as a terminal. Here the code you're looking for :
import Foundation
var squad = [1:"Arsh", 2:"Arbab", 3:"Ayush", 4:"KC", 5:"Jaski", 6:"Gunny", 7:"Harsh", 8:"Nagib", 9:"Rithin", 10:"Gursh", 11:"Sonny"]
let coming = "yes"
let notComing = "no"
var attendees = [String]()
let array = squad.map {return ($0.key, $0.value)} // Turn the dictionary to an array of tuples
let sortedArray = array.sorted(by: {$0.0 < $1.0 }) // Sort the array by the first element of the tuple
// Ask the squad one by one
for ( _ , person) in sortedArray {
print("Are you attending \(person)?")
if let response = readLine() {
//remove white spaces and all letters lowercased so that "Yes", "yes" and "YES" would all be accepted
if response.lowercased().trimmingCharacters(in: .whitespaces) == coming {
attendees.append(person)
}
else if response.lowercased().trimmingCharacters(in: .whitespaces) == notComing {
print("Sorry to hear that")
}
else {
print("Invalid Input")
}
}
}
print("attendees : \(attendees)")
print("attendees.count = \(attendees.count)")

Related

Moving Table rows in SwiftUI

Is it possible to support moving rows in a SwiftUI Table view?
I know there's List which can optionally support selection and drag-and-drop to move single or multiple rows. Since it seems similar, I would like to do this with a Table too, but I wasn't able to find any way to do this. Is this possible in SwiftUI? And if it is, what's the best way to do it?
Where I started to figure this out was the WWDC 2021 session "SwiftUI on the Mac: Finishing Touches". I highly recommend this video, as well as the preceding one "SwiftUI on the Mac: Build the Fundamentals". The code for both sessions is available.
Since you didn't include your code to show what you want to do, I have to use my code. I have a table based on an array of an Identifiable struct called Channel. Among a number of fields which are irrelevant to this problem, there is a field "id" of type UUID.
Following the model of the WWDC video, I made an extension to Channel:
import UniformTypeIdentifiers
extension Channel {
static var draggableType = UTType(exportedAs: "com.yourCompany.yourApp.channel")
// define your own type here. don't forget to include it in your info.plist as an exported type
static func fromItemProviders(_ itemProviders: [NSItemProvider], completion: #escaping ([Channel]) -> Void) {
let typeIdentifier = Self.draggableType.identifier
let filteredProviders = itemProviders.filter {
$0.hasItemConformingToTypeIdentifier(typeIdentifier)
}
let group = DispatchGroup()
var result = [Int: Channel]()
for (index, provider) in filteredProviders.enumerated() {
group.enter()
provider.loadDataRepresentation(forTypeIdentifier: typeIdentifier) { (data, error) in
defer { group.leave() }
guard let data = data else { return }
let decoder = JSONDecoder()
guard let channel = try? decoder.decode(Channel.self, from: data)
else { return }
result[index] = channel
}
}
group.notify(queue: .global(qos: .userInitiated)) {
let channels = result.keys.sorted().compactMap { result[$0] }
DispatchQueue.main.async {
completion(channels)
}
}
}
var itemProvider: NSItemProvider {
let provider = NSItemProvider()
provider.registerDataRepresentation(forTypeIdentifier: Self.draggableType.identifier, visibility: .all) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(self)
$0(data, nil)
} catch {
$0(nil, error)
}
return nil
}
return provider
}
}
This makes an item in the table draggable. Of course, that does no good if there's nothing that will accept the drag. So, you have to make a change to your Table.
Table(selection: $selection, sortOrder: $sortOrder) {
// for clarity, I've removed the table columns
} rows: {
ForEach(document.channels) { channel in
TableRow(channel)
.itemProvider { channel.itemProvider }
}
.onInsert(of: [Channel.draggableType]) { index, providers in
Channel.fromItemProviders(providers) { channels in
document.channels.insert(contentsOf: channels, at: newIndex)
}
}
}
}
Now that will enable you to drag item or items from one window to another. You can, of course, drag within a table now, too. Unfortunately, you will end up making a copy in the new place. Not what you want to do in most cases. How to fix this? Delete the original copy! Of course, you can also run into the problem of indexing in the right place, and if you drag more than one item (from a discontinuous selection, even worse!), the results become, shall we say, undefined.
I still wanted to be able to drag multiple items from another table, so the final onInsert becomes a little more complex (Which I'm sure could be cleaned up a bot further):
Channel.fromItemProviders(providers) { channels in
var newIndex = index
let intraTableDrag = document.channels.contains(where: {$0.id == channels[0].id})
if intraTableDrag && channels.count == 1 {
let oldIndex = document.channels.firstIndex(where: {$0.id == channels[0].id})
if newIndex > oldIndex! {
newIndex -= 1
}
for channel in channels {
let channelID = channel.id
removeChannel(withID: channelID)
}
let maxIndex = document.channels.count
if index > maxIndex {
newIndex = maxIndex
}
}
if (intraTableDrag && channels.count == 1) || !intraTableDrag {
document.channels.insert(contentsOf: channels, at: newIndex)
document.setChannelLocationToArrayOrder()
}
}
}
I hope this is enough to get you started. Good luck!

Why is my app freezing when func is called

Whenever I call this function, my app freezes and nothing in the debug console prints. I am trying to get strings and a double from firebase and then stick them into an identifiable struct array. Any Ideas???
If you have another idea for storing that object, I would love to read it.
var count = Int()
var name = Array<String>()
var imageUrl = Array<String>()
var id = Array<String>()
var rating = Array<Double>()
var url = Array<String>()
var keys = 0
db.collection("parties").document(Utilities.code).addSnapshotListener { document, error in
//check for error
if error == nil {
//check if document exists
print("No error")
if document != nil && document!.exists {
print("Document Exists")
if let array = document!.get("yesName") as? Array<String> {
count = array.count
name = array
print("yesName = \(array)")
keys += 1
}
if let array = document!.get("yesImg") as? Array<String> {
imageUrl = array
print("yesImg = \(array)")
keys += 1
}
if let array = document!.get("yesId") as? Array<String> {
id = array
print("yesId = \(array)")
keys += 1
}
if let array = document!.get("yesRating") as? Array<Double> {
rating = array
print("yesRating = \(array)")
keys += 1
}
if let array = document!.get("yesUrl") as? Array<String> {
url = array
print("yesUrl = \(array)")
keys += 1
}
}
}else {
print("error = \(error!)")
}
}
while keys < 6 {
if name.count > 0 && imageUrl.count > 0 && id.count > 0 && rating.count > 0 && url.count > 0 {
for _ in 0...count {
yes.list.append(RestaurantListViewModel(name: name.first!, imageUrl: URL(string: imageUrl.first!)!, id: id.first!, rating: rating.first!, url: url.first!))
print("combined = \(yes.list.append(RestaurantListViewModel(name: name.first!, imageUrl: URL(string: imageUrl.first!)!, id: id.first!, rating: rating.first!, url: url.first!)))")
name.removeFirst()
imageUrl.removeFirst()
id.removeFirst()
rating.removeFirst()
url.removeFirst()
}
keys = 6
}
}
The snapshot listener will execute asynchronously that means in some time after the current method is finished. So the code after it is called is executed only once and just after listener creation : there is nothing yet in the arrays and keys == 0. May be this code should be inside the listener call after document is read and array updated.
Have you tried to debug while keys < 6 { circle?
Next line condition
if name.count > 0 && imageUrl.count > 0 && id.count > 0 && rating.count > 0 && url.count > 0
can easily equals to false and you get infinity circle running.
Also I see few logic issues in your code: in the first part of code you set up required fields (keys equals in a range from 0 to 6). But in the second part inside circle while keys < 6 you require all 6 fields and want to exit only after all values will have count > 0. You need to get value.count only when it needed.

How to get an array from Firestore document on Swift 5?

Please help me understand the logic reading data from the Firestore document, if one of the values is an array. I tried other answers here and sources but never came to a simple working way and understand clearly. Firestore document structure — example. And Swift class targets for saving (conditional):
struct MyStruct {
var name: String
var pages: Int
}
let part1 = [MyStruct]()
let name1 = ""
let pages1 = 0
let part2 = [MyStruct]()
let name2 = ""
let pages2 = 0
func readFirestore() { }
What should the document reader function look like to add data to existing targets in the class? Thanks in advance for any help in improving my understanding!
They helped to deal with familiar, thank you for what they are. As expected, everything is simple. But for the beginner there is nothing more difficult than simplicity 😁
func readFirestore() {
self.db.collection("example").document("book").getDocument { (document, error) in
if error == nil {
if document != nil && document!.exists {
//get all document data
guard let documentData = document!.data() else {return}
//get value-array for key "part1"
let element = documentData["part1"] as? [Any] //print -> Optional([name1, 100])
//get first element in array
guard let nameDB = element?[0] as? String else {return} //print -> name1
guard let pagesDB = element?[1] as? String else {return} //print -> 100
//append in class
part1.append(MyStruct(name: nameDB, pages: pagesDB))
name1 = nameDB
pages1 = pagesDB
}
}
}
}

Swift: Multiple conditions in IF statement

I'm currently trying to validate a sign up page using an if statement with multiple conditions.
At the same time, I want to validate the email address with a correct email format.
Below is each field should essentially be checked.
#IBAction func createAccountButton(_ sender: Any) {
//check if textfields are empty and display alert
let providedEmailAddress = emailTextField.text
let isEmailAddressValid = isValidEmailAddress(emailAddressString: providedEmailAddress!)
if ( isEmailAddressValid || (firstNameTextField.text?.isEmpty)! || (lastNameTextField.text?.isEmpty)! || (usernameTextField.text?.isEmpty)! || (phoneNumberTextField.text?.isEmpty)! || (passwordTextField.text?.isEmpty)!) {
let alertViewController = SCLAlertView().showInfo("Failed to create account", subTitle: "One or more fields are empty, please check and try again.")
} else {
SVProgressHUD.show(withStatus: "Creating account...")
SVProgressHUD.dismiss(withDelay: 4)
}
}
The variable isEmailAddressValid is linked to this function I found on this site.
// Check if email address entered is of the valid format
func isValidEmailAddress(emailAddressString: String) -> Bool {
var returnValue = true
let emailRegEx = "[A-Z0-9a-z.-_]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,3}"
do {
let regex = try NSRegularExpression(pattern: emailRegEx)
let nsString = emailAddressString as NSString
let results = regex.matches(in: emailAddressString, range: NSRange(location: 0, length: nsString.length))
if results.count == 0
{
returnValue = false
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
returnValue = false
}
return returnValue
}
However, when testing I realised when I enter a value in each field except the emailTextfield.text and click to create an account, the else part of the statement is executed.
Moreover when all fields have a value, I would assume the else part of the statement WILL execute, but the 'failed to create an account' alert is displayed instead.
What am I doing wrong and what needs to be changed?
You should replace this part
if ( isEmailAddressValid |
with
if ( !isEmailAddressValid |

How to increment String in Swift

I need to save files in an alphabetical order.
Now my code is saving files in numeric order
1.png
2.png
3.png ...
The problem is when i read this files again I read this files as described here
So I was thinking of changing the code and to save the files not in a numeric order but in an alphabetical order as:
a.png b.png c.png ... z.png aa.png ab.png ...
But in Swift it's difficult to increment even Character type.
How can I start from:
var s: String = "a"
and increment s in that way?
You can keep it numeric, just use the right option when sorting:
let arr = ["1.png", "19.png", "2.png", "10.png"]
let result = arr.sort {
$0.compare($1, options: .NumericSearch) == .OrderedAscending
}
// result: ["1.png", "2.png", "10.png", "19.png"]
If you'd really like to make them alphabetical, try this code to increment the names:
/// Increments a single `UInt32` scalar value
func incrementScalarValue(_ scalarValue: UInt32) -> String {
return String(Character(UnicodeScalar(scalarValue + 1)))
}
/// Recursive function that increments a name
func incrementName(_ name: String) -> String {
var previousName = name
if let lastScalar = previousName.unicodeScalars.last {
let lastChar = previousName.remove(at: previousName.index(before: previousName.endIndex))
if lastChar == "z" {
let newName = incrementName(previousName) + "a"
return newName
} else {
let incrementedChar = incrementScalarValue(lastScalar.value)
return previousName + incrementedChar
}
} else {
return "a"
}
}
var fileNames = ["a.png"]
for _ in 1...77 {
// Strip off ".png" from the file name
let previousFileName = fileNames.last!.components(separatedBy: ".png")[0]
// Increment the name
let incremented = incrementName(previousFileName)
// Append it to the array with ".png" added again
fileNames.append(incremented + ".png")
}
print(fileNames)
// Prints `["a.png", "b.png", "c.png", "d.png", "e.png", "f.png", "g.png", "h.png", "i.png", "j.png", "k.png", "l.png", "m.png", "n.png", "o.png", "p.png", "q.png", "r.png", "s.png", "t.png", "u.png", "v.png", "w.png", "x.png", "y.png", "z.png", "aa.png", "ab.png", "ac.png", "ad.png", "ae.png", "af.png", "ag.png", "ah.png", "ai.png", "aj.png", "ak.png", "al.png", "am.png", "an.png", "ao.png", "ap.png", "aq.png", "ar.png", "as.png", "at.png", "au.png", "av.png", "aw.png", "ax.png", "ay.png", "az.png", "ba.png", "bb.png", "bc.png", "bd.png", "be.png", "bf.png", "bg.png", "bh.png", "bi.png", "bj.png", "bk.png", "bl.png", "bm.png", "bn.png", "bo.png", "bp.png", "bq.png", "br.png", "bs.png", "bt.png", "bu.png", "bv.png", "bw.png", "bx.png", "by.png", "bz.png"]`
You will eventually end up with
a.png
b.png
c.png
...
z.png
aa.png
ab.png
...
zz.png
aaa.png
aab.png
...
Paste this code in the playground and check result. n numbers supported means you can enter any high number such as 99999999999999 enjoy!
you can uncomment for loop code to check code is working fine or not
but don't forget to assign a lesser value to counter variable otherwise Xcode will freeze.
var fileName:String = ""
var counter = 0.0
var alphabets = ["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 totalAlphaBets = Double(alphabets.count)
let numFiles = 9999
func getCharacter(counter c:Double) -> String {
var chars:String
var divisionResult = Int(c / totalAlphaBets)
let modResult = Int(c.truncatingRemainder(dividingBy: totalAlphaBets))
chars = getCharFromArr(index: modResult)
if(divisionResult != 0){
divisionResult -= 1
if(divisionResult > alphabets.count-1){
chars = getCharacter(counter: Double(divisionResult)) + chars
}else{
chars = getCharFromArr(index: divisionResult) + chars
}
}
return chars
}
func getCharFromArr(index i:Int) -> String {
if(i < alphabets.count){
return alphabets[i]
}else{
print("wrong index")
return ""
}
}
for _ in 0...numFiles {
fileName = getCharacter(counter: counter)+".png"
print(fileName)
counter += 1
}
fileName = getCharacter(counter: Double(numFiles))+".png"
print(fileName)