I am using iterators to cycle through an array of flash cards, but the iterator is going every other, for example, I tested it by entering in numbers 1-7 as new cards (in order) but when I flip through the deck it only displays 2, then 4, then 6, then back to 2. When I print out the deckIterator in the functions it gives a corresponding integer back, so for card 2 the iterator prints 2. I'm not sure If i'm using the iterator correct, could anyone point me in the right direction?
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
deckArray.removeAll()
deckIterator = nil
do {
fetched = try context.fetch(Card.fetchRequest())
for each in fetched {
let term = each.term
let definition = each.definition
termLabel.text = each.term!
definitionLabel.text = each.definition!
let Card = card(term: term!, definition: definition!)
deckArray.append(Card)
}
} catch {
print(error)
}
deckIterator = deckArray.makeIterator()
}
#IBAction func leftSwipe(_ sender: UISwipeGestureRecognizer) {
getNextCardPlease()
self.definitionLabel.isHidden = true
alreadyFlipped = false
}
func getNextCardPlease(){
if(deckIterator?.next() == nil){
fetchData()
} else {
let next = deckIterator?.next()
termLabel.text = next?.term
definitionLabel.text = next?.definition
}
}
Every time you call next(), we iterate. So every call to getNextCardPlease iterates twice. Count them:
if(deckIterator?.next() == nil){ // 1
fetchData()
} else {
let next = deckIterator?.next() // 2
The problems was once if(deckIterator?.next() == nil) gets called, the iterator iterates. Iterators do not work like linked lists.
Related
Suppose you have an array, and you want to iterate over each element in the array and call a function obj.f which accepts that element as a parameter.
f is asynchronous, and completes nearly instantly, but it invokes a callback handler found in obj.
What would be the best way to match each element only after the previous finishes?
Here is one way:
let arr = ...
var arrayIndex = 0
var obj: SomeObj! // Required
obj = SomeObj(handler: {
...
arrayIndex += 1
if arrayIndex < arr.count {
obj.f(arr[arrayIndex])
}
})
obj.f(arr[0]) // Assumes array has at least 1 element
This works fine, but isn't ideal.
I could use a DispatchSemaphore, but that isn't great because it blocks the current thread.
Also, the reason why each operation must run only when the previous has finished is because the api I'm using requires it (or it breaks)
I was wondering if there was a better/more elegant way to accomplish this?
You say:
Suppose you have an array, and you want to iterate over each element in the array and call a function ... which accepts that element as a parameter.
The basic GCD pattern to know when a series of asynchronous tasks are done is the dispatch group:
let group = DispatchGroup()
for item in array {
group.enter()
someAsynchronousMethod { result in
// do something with `result`
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
// note, don't use the results here, because the above all runs asynchronously;
// return your results in the above `notify` block (e.g. perhaps an escaping closure).
If you wanted to constrain this to, say, a max concurrency of 4, you could use the non-zero semaphore pattern (but make sure you don't do this from the main thread), e.g.
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)
DispatchQueue.global().async {
for item in array {
group.enter()
semaphore.wait()
someAsynchronousMethod { result in
// do something with `result`
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
}
An equivalent way to achieve the above is with a custom asynchronous Operation subclass (using the base AsynchronousOperation class defined here or here), e.g.
class BarOperation: AsynchronousOperation {
private var item: Bar
private var completion: ((Baz) -> Void)?
init(item: Bar, completion: #escaping (Baz) -> Void) {
self.item = item
self.completion = completion
}
override func main() {
asynchronousProcess(bar) { baz in
self.completion?(baz)
self.completion = nil
self.finish()
}
}
func asynchronousProcess(_ bar: Bar, completion: #escaping (Baz) -> Void) { ... }
}
Then you can do things like:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4
let completionOperation = BlockOperation {
// do something with all the results you gathered
}
for item in array {
let operation = BarOperation(item: item) { baz in
// do something with result
}
operation.addDependency(completionOperation)
queue.addOperation(operation)
}
OperationQueue.main.addOperation(completion)
And with both the non-zero semaphore approach and this operation queue approach, you can set the degree of concurrency to whatever you want (e.g. 1 = serial).
But there are other patterns, too. E.g. Combine offers ways to achieve this, too https://stackoverflow.com/a/66628970/1271826. Or with the new async/await introduced in iOS 15, macOS 12, you can take advantage of the new cooperative thread pools to constrain the degree of concurrency.
There are tons of different patterns.
you could try using swift async/await, something like in this example:
struct Xobj {
func f(_ str: String) async {
// something that takes time to complete
Thread.sleep(forTimeInterval: Double.random(in: 1..<3))
}
}
struct ContentView: View {
var obj: Xobj = Xobj()
let arr = ["one", "two", "three", "four", "five"]
var body: some View {
Text("testing")
.task {
await doSequence()
print("--> all done")
}
}
func doSequence() async {
for i in arr.indices { await obj.f(arr[i]); print("--> done \(i)") }
}
}
I'm building swift quiz app I want to show random questions with no repeat.
var sorular: Array = ["soru1","soru2","soru3","soru4","soru5"]
var gorulensoru = [Int]()
var sayac: Int = 0
var sorularcevaplar = ["D","Y","D","Y","D"]
var cevaplar: Array<Any> = []
var dogru: Int = 0
var yanlis: Int = 0
func chooseRandom() -> String {
if gorulensoru.count == sorular.count { return "" }
let randomItem = Int(arc4random() % UInt32(sorular.count)) //get
if (gorulensoru.contains(randomItem)) {
return chooseRandom()
}
let requiredItem = sorular[randomItem]
gorulensoru.append(randomItem)
return requiredItem
}
override func viewDidLoad() {
super.viewDidLoad()
soruText.text = chooseRandom()
}
What is the problem in my code? I'm tried insert selected random item to inside gorulensoru array but it shows again selected item
if (gorulensoru.contains(randomItem)) {
return chooseRandom()
}
This statement doesn't run.
Your code only runs once.
Also, you shouldn't include potential infinite recursive calls because it can easily get to a level where it causes a hang or crash.
Use shuffle() and then iterate over the array.
Change this and it may work:
let requiredItem = sorular[randomItem]
gorulensoru.append(requiredItem)
My code below is trying to index every new entry to the array when the button is pressed. So automatically sort the element when the button is pressed.
var arrayOfInt = [Int]()
#IBAction func submitText(_ sender: Any) {
if let text = enterText.text {
if let number = Int(text) {
var index = 0
for num in arrayOfInt {
if num > number {
arrayOfInt.insert(number, at: index)
break
}
index += 1
}
print(arrayOfInt)
} else {
print("Please enter number")
}
}}
When printed this is what is coming out []. None of the numbers are printed.
Initially arrayOfInt is empty array. So it will never go inside this as the array is empty
for num in arrayOfInt {
//Whatever is here
}
Your logic is also wrong for whatever you are trying to achieve.
Array has already sort(by:(Element, Element) -> Bool) method
You better write the code as follows:-
var arrayOfInt = [Int]()
#IBAction func submitText(_ sender: Any) {
if let text = enterText.text , let number = Int(text) {
arrayOfInt.append(number)
arrayOfInt.sort { return $0 > $1 } //Modify accordingly the order you want
print(arrayOfInt)
} else {
print("Please enter number")
}
}
Your arrayOfInt variable has no elements, it's empty... so the for loop won't iterate over a zero index array, it would have no sense... that's why the code inside the curly brackets is never executed.
If you would have debugged your code with either a breakpoint or using a print statement this issue would have been shown by itself.
I would avoid using if let every time the guard statement could solve the same. Basically because it looks more readable and it's kind of the Swift approach... at the same time you are avoiding the pyramid of doom.
My suggestion would be:
var arrayOfInt = [Int]()
#IBAction func submitText(_ sender: Any) {
guard let text = enterText.text else {
print("Please enter a number")
return
} // guard
guard let number = Int(text) else {
print("Can't creates an integer value from the given string.")
return
} // guard
arrayOfInt.append(number)
arrayOfInt.sort() // From lowest to highest by default.
//arrayOfInt.sort { $0 < $1 } // From lowest to highest using a higher order function.
//arrayOfInt.sort { $0 > $1 } // From highest to lowest using a higher order function.
} // submitText
I am trying to load, then modify and resave an array.
Here is code, modify func is the top one:
func modifyUserGroupsRequestee(){
print("step2")
acceptedUsersArray.append(groupNameLbl.text!)
//error
userGroupRecordToUpdate.setObject(acceptedUsersArray as CKRecordValue?, forKey: "userGroups")
database.save(recordToUpdate) { (savedRecord, error) in
if error != nil{
print(error.debugDescription)
}else{
print("SAVED RECORD")
}
}
}
func resaveUserGroups(){
print(addedUser)
print("step1")
// add group to requestees user groups
let pred = NSPredicate(format: "username = %#", "\(addedUser)")
let query = CKQuery(recordType: "PersonalUser", predicate: pred)
let operation = CKQueryOperation(query: query)
//operation.resultsLimit = CKQueryOperationMaximumResults
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil{
self.userGroupRecordToUpdate = record
// self.acceptedUsersArray = (record.object(forKey: "userGroups") as! Array)
print("usergroup names:: \(self.acceptedUsersArray)")
if let acceptedUserArrays = record.object(forKey: "userGroups") as? [String] {
// self.feedTableView.reloadData()
self.acceptedUsersArray = acceptedUserArrays
print("looks like we r going forward")
self.modifyUserGroupsRequestee()
// }
//self.feedTableView.reloadData()
print(groupNames.count)
print(self.acceptedUsersArray)
}
}
database.add(operation)
//self.tableView.reloadData()
// print(leaderboardInfo.count)
}
}
The function prints step1 but never gets to step2. In the bottom function, I have an if let statement I tried to create to solve my nil issue (I commented my previous code above that line- self.acceptedUsersArray... Anyway, I believe I am implementing the if let statement incorrectly, because no data is loaded, even though there is data in cloud kit.
And I do have my personal user cloudKit records set up, here's a pic:
You should try to keep your code always indented consistently.
(In Xcode editor, Cmd+A (Select All), then Ctrl+I (Re-Indent).)
With confusing comments removed, your resaveUserGroups shows as:
func resaveUserGroups() {
print(addedUser)
print("step1")
// add group to requestees user groups
let pred = NSPredicate(format: "username = %#", "\(addedUser)")
let query = CKQuery(recordType: "PersonalUser", predicate: pred)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
self.userGroupRecordToUpdate = record
print("usergroup names:: \(self.acceptedUsersArray)")
if let acceptedUserArrays = record.object(forKey: "userGroups") as? [String] {
self.acceptedUsersArray = acceptedUserArrays
print("looks like we r going forward")
self.modifyUserGroupsRequestee()
print(groupNames.count)
print(self.acceptedUsersArray)
}
}
database.add(operation)
}
}
Omitting some parts to clarify:
func resaveUserGroups() {
//...
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
//...
}
database.add(operation)
}
}
The line database.add(operation) exists inside the recordFetchedBlock.
You may need to fix some more parts (that's another story), but at least, you need to move the line out of the closure to execute the operation you have created:
func resaveUserGroups() {
//...
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
//...
}
//database.add(operation)
} //↓
database.add(operation)
}
I just solved it. Apparently there always needs to be some kind of value inside the array even if it was just created. I was trying to query an array that existed in the recordType, but not yet under the specific record.
Hello I am trying to take an array I have and update a label to display each element in the array one second apart. An example would be me having the array [3,6,2] and my label would show 3 then wait a second and show 6 then wait a second and show 2. I've seen many NSTimer examples with update functions doing things like this but only with an incrementation on a number, never trying to parse an array. Can anyone help?
Update
I am calling my timer in a UIButton and am running into a problem. The timer works fine but my code in the button function under my timer runs before the timer and update function. My code below generates a random array of numbers then should display them one second apart. It is doing this correct but my print statement under my timer is running before it updates and displays the numbers in the textbook. I do not know why? Is the timer running on a different thread?
func updateCountdown() {
if(numbersIndex <= numberLimit){
self.counter.text = String(numbers[numbersIndex])
}else if(numbersIndex == numberLimit+1){
self.counter.text = ""
}else{
timer!.invalidate()
timer=nil
}
numbersIndex+=1
}
#IBAction func startButton(_ sender: AnyObject){
startB.setTitle("", for: .normal)
var highestLength = 3
//for trialNumber in 0...11{
var numberSequence = Array(repeating:11, count: highestLength)
for count in 0...highestLength-1{
var randomNumber = Int(arc4random_uniform(10))
if(count>0){
while(numberSequence.contains(randomNumber) || randomNumber-1 == numberSequence[count-1]){
randomNumber = Int(arc4random_uniform(10))
}
}
numberSequence[count] = randomNumber
}
print(numberSequence)
self.numbers = numberSequence
self.numbersIndex = 0
self.numberLimit = numberSequence.count-1
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
print("this should be last")
//do other stuff too
//}
}
you can do something like this.
var counter = 0
var list: [String] = ["Eggs", "Milk"]
override func viewDidLoad() {
super.viewDidLoad()
var timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
func update() {
label.text = "\(list[counter])"
counter += 1
if (counter >= list.count) {
counter = 0
}
}
this is assumming the name of you Label is label