realm array index out of bounds - ios

I want to save data into realm if the user clicks on a button. Moreover the view should be updated if the user clicks that button. I have the following code:
#IBAction func saveAction(_ sender: UIButton) {
if currentLogWeightTextField.text!.isEmpty || currentLogRepsTextField.text!.isEmpty || currentLogRPETextField.text!.isEmpty {
errorLabel.isHidden = false
return
}
else{
if let weight = Float(currentLogWeightTextField.text!), let reps = Int(currentLogRepsTextField.text!), let rpe = Float(currentLogRPETextField.text!){
errorLabel.isHidden = true
let setToSave = excercisesFromPlan![excerciseCounter].sets[setCounter]
do{
try realm.write{
setToSave.weight = weight
setToSave.repeats = reps
setToSave.rpe = rpe
}
}
catch{
print(error)
}
}
else{
errorLabel.isHidden = false
return
}
if setCounter < excercisesFromPlan![excerciseCounter].sets.count{
setCounter += 1
setupLabels()
print(setCounter)
print(excercisesFromPlan![excerciseCounter].sets.count)
}
else{
let finished = plan!.excercises.count - 1
if excerciseCounter == finished{
performSegue(withIdentifier: SegueIdentifier.finishedWorkout, sender: nil)
return
}
else{
excerciseCounter += 1
setCounter = 1
setupLabels()
}
}
}
}
This is my setupLabel method:
func setupLabels(){
if let excercise = excercisesFromPlan?[excerciseCounter]{
excerciseNameLabel.text = "\(excercise.name)"
setsNumberLabel.text = "\(setCounter)/\(excercise.sets.count)"
}
}
These are the relevant properties:
var excercisesFromPlan: List<Excercise>?
var plan: TrainingPlan?
var excerciseCounter = 0
var setCounter = 1
excercisesFromPlan = plan?.excercises
The plan property is given through a segue.
These are my model classes:
class TrainingPlan: Object {
dynamic var trainingPlanID = NSUUID().uuidString
dynamic var routine: Routine?
dynamic var workout: Workout?
dynamic var selected = false
dynamic var name = ""
dynamic var trainingPlanDescription = ""
dynamic var creationDate = Date(timeIntervalSince1970: 1)
dynamic var lastChangeDate = Date(timeIntervalSince1970: 1)
dynamic var lastUsed = Date(timeIntervalSince1970: 1)
let excercises = List<Excercise>()
override class func primaryKey() ->String?{
return "trainingPlanID"
}
}
class Excercise: Object {
dynamic var excerciseID = NSUUID().uuidString
dynamic var trainingsplan: TrainingPlan?
dynamic var selected = false
dynamic var name = ""
dynamic var excerciseDescription = ""
dynamic var muscleGroup = ""
dynamic var record = 0
dynamic var picture: NSData?
dynamic var copied = false
let sets = List<TrainingSet>()
override class func primaryKey() ->String?{
return "excerciseID"
}
override static func indexedProperties() -> [String] {
return ["name"]
}
}
I have the problem that if I use this code and click the save button, the Labels are updating right and the counter are also working. The only problem is that the else statement
else{
let finished = plan!.excercises.count - 1
if excerciseCounter == finished{
performSegue(withIdentifier: SegueIdentifier.finishedWorkout, sender: nil)
return
}
else{
excerciseCounter += 1
setCounter = 1
setupLabels()
}
}
is never been called. I'm searching for the problem for a few hours now but I can't find it..
Strangely, if I comment out the following from the save function it works perfectly and the else statement is called right:
if let weight = Float(currentLogWeightTextField.text!), let reps = Int(currentLogRepsTextField.text!), let rpe = Float(currentLogRPETextField.text!){
errorLabel.isHidden = true
let setToSave = excercisesFromPlan![excerciseCounter].sets[setCounter]
do{
try realm.write{
setToSave.weight = weight
setToSave.repeats = reps
setToSave.rpe = rpe
}
}
catch{
print(error)
}
}
else{
errorLabel.isHidden = false
return
}
Sorry for so much code.. Does anyone know why this is not working? Thanks in advance!

Related

How to filter Realm Results<Object>

I use mongoDB to store data. I have a Result array, which array contains multiple same keyword results.
class Keywords: Object {
#objc dynamic var name: String = ""
#objc dynamic var date: Date = Date()
#objc dynamic var rank: Int = 0
#objc dynamic var requestedURL: String = ""
}
Users can not send requests more than once for the same keyword name.
let action = UIAlertAction(title: "Add", style: .default) { action in
if let textSafe = text.text {
let textPrefix = textSafe.removeWhitespace()
if self.keywordModel.keywordNames.count > 0 {
if self.keywordModel.keywordNames.contains(textSafe) {
return
} else {
self.seo.fetchSEO(keyword: textPrefix, requestURL: self.selectedDomain!.domainName, start: 1)
}
} else {
self.seo.fetchSEO(keyword: textPrefix, requestURL: self.selectedDomain!.domainName, start: 1)
}
}
}
My codes are working fine up to this part.
But when users want to refresh to update results there I need to write filter results or somehow must get only the last recent results if keywords are multiple times stored in the database. By the way, I need multiplied keyword data results since I want to use them for date progress charts in the future.
So simply I need to save all results but I want to show only more recent results on UI.
#objc func refresh(_ sender: AnyObject) {
var keywordArray = [String]()
keywordModel.keywordNames.forEach { keyword in
keywordArray.append(keyword)
}
keywordModel.keywordNames = [String]()
keywordModel.keywordRanks = [Double]()
keywordArray.forEach { keyword in
self.seo.fetchSEO(keyword: keyword, requestURL: self.selectedDomain!.domainName)
}
print(keyword!)
let deadLine = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadLine) {
self.refreshControl?.endRefreshing()
}
}
And for further detail, I transfer results to KeywordModel for some calculations, and i manage all tableview through the KeywordModel codes below.
struct KeywordModel {
var keywordRanks = [Double]()
var averageRank: Double? = 0.0
var keywordCount: Int? = 0
var keywordNames = [String]()
var keywordCountString: String? {
return String(keywordCount!)
}
var averageRankString: String? {
return String(format: "%.01f", averageRank!)
}
mutating func averageOfRanks(resultKeyword: Results<Keywords>?) {
keywordCount = resultKeyword?.count
var n = 0
var raw = [Double]()
while n < resultKeyword!.count {
raw.append(Double(resultKeyword![n].rank))
n += 1
}
keywordRanks = raw
let rankSum = keywordRanks.reduce(0, +)
averageRank = rankSum / Double(keywordRanks.count)
}
mutating func saveKeywords(from results: Results<Keywords>?) {
if results!.count > 0 {
var n = 0
var raw = [String]()
while n < results!.count {
raw.append(results![n].name)
n += 1
}
keywordNames = raw
}
}
And there is my loadData method, where I can apply a filter.
func loadData() {
keyword = selectedDomain?.keywords.sorted(byKeyPath: "name")
statisticCalculate(keyword: keyword)
tableView.reloadData()
}

Realm swift: Empty realm after relaunching app

I started a new project and tried to implement Realm but I can't get it to work properly. My problem is that when I kill my app and relaunch it, all my previously added objects have disappeared and I get empty results from realm.objects.
class RealmManager {
static let shared = RealmManager()
let realm: Realm
init() {
realm = try! Realm()
}
func write(_ completion: ()->Void) {
do {
try realm.write() {
completion()
}
} catch {
print(error)
}
}
func add(_ object: Object) {
realm.add(object)
}
func delete(_ object: Object) {
realm.delete(object)
}
func objects<Element>(_ type: Element.Type) -> Results<Element> where Element : Object {
return realm.objects(type)
}
}
I created this singleton so I don't have to repeat this realm = try! Realm()everywhere in my code. I have this exact same class in another project which works fine.
My model looks like this :
class PrepFile: Object {
#objc dynamic var creationDate: Date = Date()
#objc dynamic var lastModificationDate: Date = Date()
#objc dynamic var title: String = "Pas de titre"
#objc dynamic var activityKind: String = ""
#objc dynamic var seanceNumber: Int = 0
#objc dynamic var level: String = ""
#objc dynamic var duration: Int = 0
#objc dynamic var date: Date = Date()
#objc dynamic var cycle: Int = 0
#objc dynamic var mainGoal: String = ""
#objc dynamic var specificGoal: String = ""
#objc dynamic var material: String = ""
#objc dynamic var isDraft: Bool = true
convenience init(title: String? = nil, activityKind: String? = nil, seanceNumber: Int? = nil, level: String? = nil, duration: Int? = nil, date: Date? = nil, cycle: Int? = nil, mainGoal: String? = nil, specificGoal: String? = nil, material: String? = nil, phases: [Phase] = [], isDraft: Bool = true) {
self.init()
if let tt = title {
self.title = tt
}
if let ak = activityKind {
self.activityKind = ak
}
if let sn = seanceNumber {
self.seanceNumber = sn
}
if let lv = level {
self.level = lv
}
if let dt = duration {
self.duration = dt
}
if let dt = date {
self.date = dt
}
if let cl = cycle {
self.cycle = cl
}
if let mg = mainGoal {
self.mainGoal = mg
}
if let sg = specificGoal {
self.specificGoal = sg
}
if let mt = material {
self.material = mt
}
self.isDraft = isDraft
}
required init() {
}
}
Then in my VC here's what I do :
class PrepFileListViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
lazy var prepFiles: Results<PrepFile> = { RealmManager.shared.objects(PrepFile.self) }()
var completePrepFiles: [PrepFile] = []
var draftPrepFiles: [PrepFile] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
RealmManager.shared.write {
for file in prepFiles {
RealmManager.shared.delete(file)
}
}
RealmManager.shared.write() {
RealmManager.shared.add(PrepFile(title: "Fiche de prep 1"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 2"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 3"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 4"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 5"))
}
}
override func viewWillAppear(_ animated: Bool) {
prepFiles = RealmManager.shared.objects(PrepFile.self)
completePrepFiles = prepFiles.filter({ !$0.isDraft })
draftPrepFiles = prepFiles.filter({ $0.isDraft })
tableView.reloadData()
}
}
Now when I run this, it works fine. My PrepFile are added to Realm and I retrieve them alright with my RealmManager.shared.objects(PrepFile.self). Now, I comment the part where I delete/add my files in the viewDidLoad and I get nothing. I don't get empty objects from RealmManager.shared.objects(PrepFile.self), I get an empty result like nothing was ever saved there.
What am I doing wrong ?
I am using Xcode 12 and running my app on an iPhone 11 / 13.3 simulator. Realm version is 5.5.0.
Hmm from what I can guess, you either have somewhere else a configuration for realm to be "inMemoryIdentifier" or you do not set the proper configuration with fileURL at beggining of the app.
Configuration local realm: https://realm.io/docs/swift/latest/#realms

How to check if every two flipped cards' current title of their buttons in an array are matching in a card matching game

I am adding in some functionalities for this iOS swift matching card game and I need to check if the first 2 buttons flipped over match. They have four types of emojis for eight cards, which means there are 4 pairs of matches. I am having trouble finding out how to check if the cards match and when they match, I need the background color of the buttons to opaque (invisible). Everything else works except the empty if statement in the concentration class within the chooseCard function. That is where I need help.
Here's all the code so you can see whats related to what:
class ViewController: UIViewController {
lazy var game = Concentration(numberOfPairsOfCards: (cardButtons.count + 1) / 2)
var flipCount = 0 {
// Property Observer
didSet { flipLabel.text = "Flips: \(flipCount)" }
}
#IBOutlet weak var flipLabel: UILabel!
#IBOutlet var cardButtons: [UIButton]!
#IBAction func touchCard(_ sender: UIButton) {
flipCount+=1
if let cardNumber = cardButtons.firstIndex(of: sender) {
game.chooseCard(at: cardNumber)
updateViewFromModel()
} else {
print ("chosen card was not in cardButtons")
}
}
var emojiChoices = ["๐Ÿ‘ป", "๐ŸŽƒ", "๐Ÿ™๐Ÿพ", "๐Ÿฆ†"]
var emoji = [Int:String]()
func emoji(for card: Card) -> String {
if emoji[card.identifier] == nil, emojiChoices.count > 0 {
let randomIndex = Int(arc4random_uniform(UInt32(emojiChoices.count)))
emoji[card.identifier] = emojiChoices.remove(at: randomIndex)
}
return emoji[card.identifier] ?? "?"
}
func updateViewFromModel() {
for index in cardButtons.indices {
let button = cardButtons[index]
let card = game.cards[index]
if card.isFaceUp {
button.setTitle(emoji(for: card), for: UIControl.State.normal)
button.backgroundColor = UIColor.white
}
else {
button.setTitle("", for: UIControl.State.normal)
button.backgroundColor = UIColor.orange
}
}
}
}
import Foundation
class Concentration {
var cards = [Card]()
func chooseCard(at index: Int) {
if cards[index].isFaceUp {
cards[index].isFaceUp = false
}
else {
cards[index].isFaceUp = true
}
if cards[index].isFaceUp && cards[index].isMatched {
}
}
init(numberOfPairsOfCards: Int) {
for _ in 0..<numberOfPairsOfCards {
let card = Card()
cards += [card,card]
}
//TODO: Shuffle cards
cards.shuffle()
}
}
import Foundation
struct Card {
var isMatched = false
var isFaceUp = false
var identifier: Int
static var identifierFactory = 0
static func getUniqueIdentifier() -> Int {
Card.identifierFactory += 1
return Card.identifierFactory
}
init() {
self.identifier = Card.getUniqueIdentifier()
}
}
In your concentration class you will check for faceUp card indexes
Change your Concentration from class to Struct
struct Concentration { // instead of class Concentration
private var indexOfFaceUpCard: Int? {
get {
let faceUpCardIndices = cards.indices.filter { cards[$0].isFaceUp }
return faceUpCardIndices.count == 1 ? faceUpCardIndices.first : nil
} set {
for index in cards.indices {
cards[index].isFaceUp = (index == newValue)
}
}
}
Then you have mutating chooseCard method
mutating func chooseCard(at index: Int)
In your chooseCard method you check for matching
if !cards[index].isMatched {
if let matchIndex = indexOfFaceUpCard, matchIndex != index {
if cards[matchIndex] == cards[index] {
cards[matchIndex].isMatched = true
cards[index].isMatched = true
}
cards[index].isFaceUp = true
} else {
indexOfFaceUpCard = index
}
}
So your method look like this
mutating func chooseCard(at index: Int) {
if !cards[index].isMatched {
if let matchIndex = indexOfFaceUpCard, matchIndex != index {
if cards[matchIndex] == cards[index] {
cards[matchIndex].isMatched = true
cards[index].isMatched = true
}
cards[index].isFaceUp = true
} else {
indexOfFaceUpCard = index
}
}
}
Update your card struct
struct Card: Hashable {
var isMatched = false
var isFaceUp = false
var identifier: Int
static var identifierFactory = 0
static func getUniqueIdentifier() -> Int {
Card.identifierFactory += 1
return Card.identifierFactory
}
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.identifier == rhs.identifier
}
init() {
self.identifier = Card.getUniqueIdentifier()
}
}

How to remove "..." from my array?

In my calculator app I ran into a problem where I want ... to show in my array but only when the if statement for resultIsPending is true. Then after that I want the ... to be deleted. How can I do this in Swift? Here is the code of my ViewController.swift:
#IBOutlet weak var sequence: UILabel!
#IBOutlet weak var display: UILabel!
var userInTheMiddleOfTyping = false
var resultIsPending:Bool = false
var elements = [String]()
//var sequenceArray:Array = []
#IBAction func clear(_ sender: Any) {
display.text = " "
elements.removeAll()
elements = elements.filter{$0 != "\(String(describing: display.text))"}
sequence.text = elements.joined()
}
override func viewDidLoad() {
}
#IBAction func touchDigit(_ sender: UIButton) {
let digit = sender.currentTitle!
elements.append(digit)
combineToMakeOperationHistory()
if userInTheMiddleOfTyping{
let textCurrentlyInDisplay = display!.text!
display!.text = textCurrentlyInDisplay + digit
} else {
display!.text = digit
userInTheMiddleOfTyping = true
}
}
var displayValue: Double{
get{
return Double(display.text!)!
}
set{
display.text = String(newValue)
}
}
private var brain = CalculatorBrain()
#IBAction func performOperation(_ sender: UIButton) {
let perSender = sender.currentTitle!
elements.append(perSender)
combineToMakeOperationHistory()
if perSender == "+" || perSender == "รท" || perSender == "ร—" || perSender == "-" || perSender == "^"{
resultIsPending = true
}
if userInTheMiddleOfTyping{
brain.setOperand(displayValue)
userInTheMiddleOfTyping = false
}
userInTheMiddleOfTyping = false
if let mathematicalSymbol = sender.currentTitle{
brain.performOperation(mathematicalSymbol)
}
if brain.result != nil{
displayValue = brain.result!
}
}
func combineToMakeOperationHistory() {
if resultIsPending{ // this is the if statement
elements.append("...")
}else if resultIsPending == false{
}
sequence.text = elements.joined()
}
You can filter your elements array and remove the "...".
elements = elements.filter({ $0 != "..." })
Whenever you want to remove the occurrence of a String value.
you can uses something like hat
var resultIsPending:Bool = false{
didSet(isPending) {
if isPending {
elements.append("...")
} else {
elements.dropLast()
}
}
}
Don't combine data that are not of the same type. There is no reason to put ... into the array of elements:
func combineToMakeOperationHistory() {
var sequenceText: String = elements.joined()
if (resultIsPending) {
sequenceText += "..."
}
sequence.text = sequenceText
}
Since we are not appending ... to the array, we don't have to remove it.

Returning Bool from one class to another in Swift

I have here a piece of code, and i can't seem to figure out why it doesn't work.
The viewController is supposed to check whether a switch is turned on or off.
class ViewControllerFirst: UIViewController {
#IBAction func friendFunc(){
if friendSwitch.on{
friendOn = true
} else {
friendOn = false
}
}
func returnFriend() -> Bool{
return friendOn
}
}
And if the switch is turned on, an Array should be added to tempArray.
import Foundation
struct DareBook {
let fview = ViewControllerFirst()
let dareArrayFriend = [""]
func randomDare() -> String{
var tempArray = [""]
if ViewControllerFirst().returnFriend() == true{
tempArray += dareArrayFriend
}
var unsignedArrayCount = UInt32(tempArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
return tempArray[randomNumber]
}
}
i'm not getting any errormessages when i build, but it singles out this line:
func returnFriend() -> Bool{
You are creating a new view controller when you call the function. That is probably not what you want.
func randomDare() -> String{
var tempArray = [""]
// if ViewControllerFirst().returnFriend() == true{ <--- this line can't be right
if fview.returnFriend() == true{
tempArray += dareArrayFriend
}
var unsignedArrayCount = UInt32(tempArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
return tempArray[randomNumber]
}
}
It looks like you are instantiating a new view controller every time you check that boolean, (ViewControllerFirst() seems like it should be fview at the least and I don't think that fview is the actual ViewController that you want) so it seems to me that it would always be false.

Resources