How to increment private variable in Model? - ios

In my struct Main Model (MainModel.swift):
private var charLevel: Int = 1
mutating func setCharLevel(_ value: Int) {
charLevel = value
}
var getCharLevel: Int? {
get {
return charLevel
}
}
In my firstViewController.swift:
private var MyModel = MainModel()
let sb = UIStoryBoard(name: "Main", bundle: nil)
#IBAction func addLevel(_ sender: UIButton){
if let secondVC = sb.instantiateViewController(withIdentifier: "SecondVC") as? SecondViewController {
self.present(secondVC, animated: true, completion: nil)
let charNewLevel = MyModel.getCharLevel! + 1
MyModel.setCharlevel(charNewLevel)
}
}
The SecondVC has only one label that shows charLevel from MainModel.swift and a button with self.dismiss that returns to firstViewController. So it just goes in a loop / circle. My problem is the level only goes to 2, even if I do 3+ runs, what am I missing? I would like it to increment by 1 every run (1st run: 2, 2nd run: 3, 3rd run: 4 and so on). Thank you from a learning student.
EDIT:
firstViewController -> ViewController (original name)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var battleDescription: UILabel!
#IBOutlet weak var heroHealthLabel: UILabel!
#IBOutlet weak var enemyHealthLabel: UILabel!
#IBOutlet weak var byBattleDescription: UILabel!
private var QuestModel = MainModelQuest()
let sb = UIStoryboard(name: "Main", bundle: nil)
override func viewDidLoad() {
super.viewDidLoad()
QuestModel.setHeroHealth(1000)
QuestModel.setHeroMaxHealth(1000)
QuestModel.setEnemyHealth(150)
if let initialTempHeroHealth = QuestModel.getHeroHealth {
checkHeroLabel = initialTempHeroHealth
}
if let initialTempEnemyHealth = QuestModel.getEnemyHealth {
checkEnemyLabel = initialTempEnemyHealth
}
}
var checkHeroLabel: Int {
get {
return Int(heroHealthLabel.text!)!
} set {
heroHealthLabel.text = String(newValue)
}
}
var checkEnemyLabel: Int {
get {
return Int(enemyHealthLabel.text!)!
} set {
enemyHealthLabel.text = String(newValue)
}
}
#IBAction func attackOption(_ sender: UIButton) {
let tempHeroRandomX = arc4random_uniform(5) + 1
let tempHeroRandomY = arc4random_uniform(5) + 1
let tempEnemyRandomX = arc4random_uniform(5) + 1
let tempEnemyRandomY = arc4random_uniform(5) + 1
if tempHeroRandomX == tempHeroRandomY {
battleDescription.text = "Hero's attacked missed."
QuestModel.setHeroDamage(0)
} else {
if let chosenAttack = sender.currentTitle {
switch chosenAttack {
case "Attack1":
// 30 - 40
let tempHeroRandAttack = arc4random_uniform(11) + 30
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
case "Attack2":
// 20 - 30
let tempHeroRandAttack = arc4random_uniform(11) + 20
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
case "Attack3":
// 10 - 20
let tempHeroRandAttack = arc4random_uniform(11) + 10
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
case "Attack4":
// 1 - 10
let tempHeroRandAttack = arc4random_uniform(10) + 1
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
default:
break
}
}
}
if tempEnemyRandomX == tempEnemyRandomY {
byBattleDescription.text = "The enemy's attacked missed"
QuestModel.setEnemyDamage(0)
} else {
let tempEnemyRandAttack = arc4random_uniform(11) + 10
QuestModel.setEnemyDamage(Int(tempEnemyRandAttack))
}
if QuestModel.getEnemyDamage! > QuestModel.getHeroHealth! {
QuestModel.setHeroHealth(0)
if let heroKilledVC = sb.instantiateViewController(withIdentifier: "HeroFaintedVC") as? ThirdViewController {
self.present(heroKilledVC, animated: true, completion: nil)
}
} else {
let heroDamage = QuestModel.getHeroHealth! - QuestModel.getEnemyDamage!
QuestModel.setHeroHealth(heroDamage)
}
if QuestModel.getHeroDamage! > QuestModel.getEnemyHealth! {
QuestModel.setEnemyHealth(0)
if let enemyKilledVC = sb.instantiateViewController(withIdentifier: "EnemyFaintedVC") as? SecondViewController {
self.present(enemyKilledVC, animated: true, completion: nil)
let checkTotalExpi = QuestModel.getHeroExpi! + 30
if checkTotalExpi >= QuestModel.getHeroMaxExpi! {
let heroNewLevel = QuestModel.getHeroLevel! + 1
QuestModel.setHeroLevel(heroNewLevel)
let newHeroMaxExpi = QuestModel.getHeroMaxExpi! * 2
QuestModel.setHeroMaxExpi(newHeroMaxExpi)
QuestModel.setHeroExpi(0)
}
enemyKilledVC.infoObject = QuestModel.getHeroLevel
}
} else {
let enemyDamage = QuestModel.getEnemyHealth! - QuestModel.getHeroDamage!
QuestModel.setEnemyHealth(enemyDamage)
}
if QuestModel.getHeroDamage! > 0 {
if QuestModel.getEnemyHealth! <= 0 {
battleDescription.text = "Hero dealt \(QuestModel.getHeroDamage!) damage and the enemy fainted."
} else {
if let attackName = sender.currentTitle {
battleDescription.text = "Hero used " + attackName + " and dealt \(QuestModel.getHeroDamage!) damage."
}
}
}
if QuestModel.getEnemyDamage! > 0 {
if QuestModel.getHeroHealth! <= 0 {
byBattleDescription.text = "the enemy dealt \(QuestModel.getEnemyDamage!) and the hero fainted."
} else {
byBattleDescription.text = "Enemy attacked and dealt \(QuestModel.getEnemyDamage!) damage."
checkHeroLabel = QuestModel.getHeroHealth!
checkEnemyLabel = QuestModel.getEnemyHealth!
}
}
}
}
MainModel -> MainModelQuest (original name)
import Foundation
struct MainModelQuest {
// Hero Properties
private var heroHealth: Int?
private var heroMaxHealth: Int?
private var heroMana: Int?
private var heroMaxMana: Int?
private var heroStamina: Int?
private var heroMaxStamina: Int?
private var heroLevel: Int = 1
private var heroDamage: Int?
private var heroMaxDamage: Int? // Not yet used
private var heroExpi: Int = 25
private var heroMaxExpi: Int = 30
private var heroGold: Int? // Not yet used
private var heroGainedGold: Int? // Not yet used
// Enemy Properties
private var enemyHealth: Int?
private var enemyDamage: Int?
private var enemyLevel: Int? // Not yet used
mutating func setHeroHealth(_ value: Int) {
heroHealth = value
}
mutating func setHeroMaxHealth(_ value: Int) {
heroMaxHealth = value
}
mutating func setHeroLevel(_ value: Int) {
heroLevel = value
}
mutating func setHeroDamage(_ value: Int) {
heroDamage = value
}
mutating func setHeroExpi(_ value: Int) {
heroExpi = value
}
mutating func setHeroMaxExpi (_ value: Int) {
heroMaxExpi = value
}
mutating func setEnemyHealth(_ value: Int) {
enemyHealth = value
}
mutating func setEnemyDamage(_ value: Int) {
enemyDamage = value
}
var getHeroHealth: Int? {
get {
return heroHealth
}
}
var getHeroMaxHealth: Int? {
get {
return heroMaxHealth
}
}
var getHeroLevel: Int? {
get {
return heroLevel
}
}
var getHeroDamage: Int? {
get {
return heroDamage
}
}
var getHeroExpi: Int? {
get {
return heroExpi
}
}
var getHeroMaxExpi: Int? {
get {
return heroMaxExpi
}
}
var getEnemyHealth: Int? {
get {
return enemyHealth
}
}
var getEnemyDamage: Int? {
get {
return enemyDamage
}
}
}
SeconfViewController
import UIKit
class SecondViewController: UIViewController {
var infoObject: Int? {
didSet {
enemyDefeatedObject.text = "Lv. " + String(infoObject!)
}
}
#IBOutlet weak var enemyDefeatedObject: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
// Return to map
#IBAction func BacktoMapVC(_ sender: Any) {
let sb = UIStoryboard(name: "Main", bundle: nil)
if let mapVC = sb.instantiateViewController(withIdentifier: "MapVC") as? MapViewController {
self.present(mapVC, animated: true, completion: nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I understand that this may be a little bulk of code but this completes what I have been practicing so far, I may have incorrect conventions here but you are more than welcome to correct me on those point. My problem is upon running the program the 1st time, it gets correct level which is 2, 2nd run and conceding runs after are still two. Any suggestion is welcome. Thanks you.

Related

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()
}
}

Is there a way to save my firebase model from query in a dictionary in Swift?

first I want to tell you what's my goal:
It should be possible for my app users to tap the + or - Stepper in each ProductTableViewCell to add or remove products to/from Cart.
In the NewOrderViewController where the productTableView is embedded there should be a label for showing the user how many products are in Cart at all. I named it totalProductCountLabel.
Also there should be a label named totalProductPriceLabel, to show the price for all products in Cart. The Cart should be updated every time the user taps the stepper inside a cell, and when the product.counter value is 0, then the product should be automatically removed from Cart.
Yesterday I've had a solution which works perfect:
ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CartSelection {
var totalProductCount = 0
var timerEnabled = true
var timerCount = 0
#IBOutlet weak var testTableview: UITableView!
#IBOutlet weak var totalProductCountLabel: UILabel!
#IBOutlet weak var totalProductPriceLabel: UILabel!
var productArray = [Product]()
var cartDictionary = [String: Product]()
override func viewDidLoad() {
super.viewDidLoad()
testTableview.delegate = self
testTableview.dataSource = self
testTableview.allowsSelection = false
productArray.append(Product(name: "Bananen", count: 0, price: 1.09, uuid: UUID().uuidString))
productArray.append(Product(name: "Äpfel", count: 0, price: 0.81, uuid: UUID().uuidString))
productArray.append(Product(name: "Kirschen", count: 0, price: 6.34, uuid: UUID().uuidString))
productArray.append(Product(name: "Tomaten", count: 0, price: 2.68, uuid: UUID().uuidString))
productArray.append(Product(name: "Weintrauben", count: 0, price: 1.48, uuid: UUID().uuidString))
productArray.append(Product(name: "Schokolade", count: 0, price: 0.67, uuid: UUID().uuidString))
testTableview.reloadData()
timerEnabled = true
timerCount = 0
self.totalProductCountLabel.text = "Anzahl Produkte: 0 Stück"
self.totalProductPriceLabel.text = "Preis Produkte: 0.00 €"
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timerEnabled = false
timerCount = 0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
timerEnabled = false
timerCount = 5
}
func startTimer() {
_ = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
if self.timerEnabled != false && self.timerCount < 4 {
self.testTableview.reloadData()
print("Die Produktliste wurde aktualisiert!")
self.timerCount += 1
} else {
timer.invalidate()
self.timerEnabled = true
self.timerCount = 0
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TestTableViewCell
cell.product = productArray[indexPath.row]
cell.productNameLabel.text = "\(cell.product.name)"
cell.productPriceLabel.text = "\(cell.product.price)" + " € / kg"
cell.productCountLabel.text = "\(cell.product.count)"
cell.cartSelectionDelegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 188
}
func addProductToCart(product: Product, uuid: String) {
timerEnabled = false
cartDictionary[uuid] = product
if cartDictionary[uuid]?.count == 0 {
cartDictionary.removeValue(forKey: uuid)
}
calculateTotal()
}
func calculateTotal() {
var totalProductCount = 0
var totalProductPrice = 0.00
for (_,value) in cartDictionary {
totalProductCount += value.count
totalProductPrice += value.totalPrice
}
self.totalProductCountLabel.text = "Anzahl Produkte: \(totalProductCount) Stück"
self.totalProductPriceLabel.text = "Preis Produkte: \(String(format: "%5.2f", totalProductPrice)) €"
self.totalProductCount = totalProductCount
if cartDictionary.isEmpty {
print("Der Einkaufswagen ist leer")
timerEnabled = true
timerCount = 0
startTimer()
}
}
#IBAction func orderButtonTapped(_ sender: UIButton) {
if totalProductCount != 0 {
showOrderConfirmation()
var priceLabel = 0.00
print("Im Einkaufwagen sind folgende Produkte:")
for (key,value) in cartDictionary {
priceLabel += value.totalPrice
print("Produkt: \(value.name) --> \(value.count) __ Produktkennung: \(key)")
}
print("Gesamtpreis: \(priceLabel)")
}
}
func showOrderConfirmation() {
let alert = UIAlertController(title: "Bestellung aufgeben?", message: nil, preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Bestellung aufgeben", style: .default)
let cancelAction = UIAlertAction(title: "Bestellung bearbeiten", style: .destructive)
alert.addAction(okayAction)
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
}
Product Model
import UIKit
struct Product {
var name = "Produkt"
var count = 0
var price = 0.00
var totalPrice: Double {
return Double((count)) * (price)
}
var uuid = "Example: 20EE8409-230C-4E69-B366-C2EEE03998AF"
}
protocol CartSelection {
func addProductToCart(product : Product, uuid : String)
}
TableViewCell
import UIKit
class TestTableViewCell: UITableViewCell {
var cartSelectionDelegate: CartSelection?
#IBOutlet weak var productCountLabel: UILabel!
#IBOutlet weak var productNameLabel: UILabel!
#IBOutlet weak var productPriceLabel: UILabel!
#IBOutlet weak var totalProductPriceLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
#IBAction func cellTrashButtonTapped(_ sender: UIButton) {
resetCell()
}
#IBAction func changeProductCountStepper(_ sender: UIStepper) {
productCountLabel.text = Int(sender.value).description
product.count = Int(sender.value)
let totalPriceForProduct = Double(product.count) * product.price
totalProductPriceLabel.text = "Produktgesamtpreis: \(String(format: "%5.2f", totalPriceForProduct)) €"
updateChanges()
}
func updateChanges() {
cartSelectionDelegate?.addProductToCart(product: product, uuid: product.uuid)
}
func resetCell() {
productCountLabel.text = "0"
product.count = 0
totalProductPriceLabel.text = "Produktgesamtpreis: 0.00 €"
stepper.value = 0
updateChanges()
}
override func awakeFromNib() {
super.awakeFromNib()
stepper.autorepeat = true
}
var product : Product!
var productIndex = 0
}
Now my products are stored in Firebase Firestore, so I don't need the append method anymore and using a query instead.
All works as before, my products are shown in the table.
First Problem:
My Outlets from the ViewController are all nil, when pressing the stepper, so I can't see my label counting anymore. (The one in the ViewController), the labels in the cell are working.
Second Problem:
In the first code I am using a dictionary to store all the products in it ("cartDictionary").
This works in the second code, too. But only for one product. The counter in the dictionary is updating if I only tap one cells stepper, but if I tap on another cells stepper, the whole dictionary is empty and it starts with the other product again.
I can't find a solution for this problem, I know the problem is something with my ViewController is not initialized when using my outlets, but if I initialize it my dataSource crashes.
It would be very nice if there's someone helping me, because I can't go on working the last days.
Thank you very much!
My code that's not working:
Product Model
import UIKit
import FirebaseFirestore
protocol CartSelection {
func addProductToCart(product : Product, uuid : String)
}
struct Product {
var documentID: String
var shopID: String
var productName: String
var productPrice: Double
var counter: Int
var uuid: String
var totalPrice: Double {
return Double((counter)) * (productPrice)
}
}
// MARK: - Firestore interoperability
extension Product: DocumentSerializable {
/// Initializes a shop with a documentID auto-generated by Firestore.
init(shopID: String,
productName: String,
productPrice: Double,
counter: Int,
uuid: String) {
let document = Firestore.firestore().shops.document()
self.init(documentID: document.documentID,
shopID: shopID,
productName: productName,
productPrice: productPrice,
counter: counter,
uuid: uuid)
}
/// Initializes a shop from a documentID and some data, from Firestore.
private init?(documentID: String, dictionary: [String: Any]) {
guard let shopID = dictionary["shopID"] as? String,
let productName = dictionary["productName"] as? String,
let productPrice = dictionary["productPrice"] as? Double,
let counter = dictionary["counter"] as? Int,
let uuid = dictionary["uuid"] as? String else { return nil }
self.init(documentID: documentID,
shopID: shopID,
productName: productName,
productPrice: productPrice,
counter: counter,
uuid: uuid)
}
init?(document: QueryDocumentSnapshot) {
self.init(documentID: document.documentID, dictionary: document.data())
}
init?(document: DocumentSnapshot) {
guard let data = document.data() else { return nil }
self.init(documentID: document.documentID, dictionary: data)
}
/// The dictionary representation of the restaurant for uploading to Firestore.
var documentData: [String: Any] {
return [
"shopID": shopID,
"productName": productName,
"productPrice": productPrice,
"counter": counter,
"uuid": uuid
]
}
}
Product TableViewCell
import UIKit
import FirebaseFirestore
class ProductTableViewCell: UITableViewCell {
var cartSelectionDelegate: CartSelection?
#IBOutlet weak var productCountLabel: UILabel!
#IBOutlet weak var productNameLabel: UILabel!
#IBOutlet weak var productPriceLabel: UILabel!
#IBOutlet weak var totalProductPriceLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
#IBAction func cellTrashButtonTapped(_ sender: UIButton) {
resetCell()
}
#IBAction func changeProductCountStepper(_ sender: UIStepper) {
productCountLabel.text = Int(sender.value).description
product.counter = Int(sender.value)
let totalPriceForProduct = Double(product.counter) * product.productPrice
totalProductPriceLabel.text = "Produktgesamtpreis: \(String(format: "%5.2f", totalPriceForProduct)) €"
updateChanges()
}
func updateChanges() {
cartSelectionDelegate?.addProductToCart(product: product, uuid: product.uuid)
}
func resetCell() {
productCountLabel.text = "0"
product.counter = 0
totalProductPriceLabel.text = "Produktgesamtpreis: 0.00 €"
stepper.value = 0
updateChanges()
}
override func awakeFromNib() {
super.awakeFromNib()
stepper.autorepeat = true
}
var product : Product!
var productIndex = 0
}
Product TableViewDataSource
import UIKit
import FirebaseFirestore
#objc class ProductTableViewDataSource: NSObject, UITableViewDataSource {
private let products: LocalCollection<Product>
var sectionTitle: String?
public init(products: LocalCollection<Product>) {
self.products = products
}
public convenience init(query: Query, updateHandler: #escaping ([DocumentChange]) -> ()) {
let collection = LocalCollection<Product>(query: query, updateHandler: updateHandler)
self.init(products: collection)
}
public func startUpdates() {
products.listen()
}
public func stopUpdates() {
products.stopListening()
}
subscript(index: Int) -> Product {
return products[index]
}
public var count: Int {
return products.count
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitle
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.TableView.productCell, for: indexPath) as! ProductTableViewCell
cell.product = products[indexPath.row]
cell.productNameLabel.text = "\(cell.product.productName)"
cell.productPriceLabel.text = "\(cell.product.productPrice)" + " € / kg"
cell.productCountLabel.text = "\(cell.product.counter)"
cell.cartSelectionDelegate = NewOrderViewController()
return cell
}
}
NewOrderViewController
import UIKit
import FirebaseFirestore
class NewOrderViewController: UIViewController, UITableViewDelegate, CartSelection {
#IBOutlet var productTableView: UITableView!
#IBOutlet weak var totalProductCountLabel: UILabel!
#IBOutlet weak var totalProductPriceLabel: UILabel!
private var shop: Shop!
var totalProductCount = 0
var timerEnabled = true
var timerCount = 0
var cartDictionary = [String: Product]()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
productTableView.dataSource = dataSource
productTableView.delegate = self
query = baseQuery
productTableView.allowsSelection = false
productTableView.reloadData()
timerEnabled = true
timerCount = 0
totalProductCountLabel.text = "Anzahl Produkte: 0 Stück"
totalProductPriceLabel.text = "Preis Produkte: 0.00 €"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setNeedsStatusBarAppearanceUpdate()
dataSource.startUpdates()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
dataSource.stopUpdates()
timerEnabled = false
timerCount = 0
}
// MARK: - DataSource and Query
private func dataSourceForQuery(_ query: Query) -> ProductTableViewDataSource {
return ProductTableViewDataSource(query: query) { [unowned self] (changes) in
self.productTableView.reloadData()
}
}
lazy private var dataSource: ProductTableViewDataSource = {
return dataSourceForQuery(baseQuery)
}()
lazy private var baseQuery: Query = {
Firestore.firestore().products.whereField("shopID", isEqualTo: shop.documentID)
}()
fileprivate var query: Query? {
didSet {
dataSource.stopUpdates()
productTableView.dataSource = nil
if let query = query {
dataSource = dataSourceForQuery(query)
productTableView.dataSource = dataSource
dataSource.startUpdates()
}
}
}
// MARK: - Delegate Method
func addProductToCart(product: Product, uuid: String) {
timerEnabled = false
cartDictionary[uuid] = product
if cartDictionary[uuid]?.counter == 0 {
cartDictionary.removeValue(forKey: uuid)
}
calculateTotal()
}
// MARK: - Actions
#IBAction func orderButtonTapped(_ sender: UIButton) {
if totalProductCount != 0 {
showOrderConfirmation()
var priceLabel = 0.00
print("Im Einkaufwagen sind folgende Produkte:")
for (key,value) in cartDictionary {
priceLabel += value.totalPrice
print("Produkt: \(value.productName) --> \(value.counter) __ Produktkennung: \(key)")
}
print("Gesamtpreis: \(priceLabel)")
}
}
// MARK: - Functions
func showOrderConfirmation() {
let alert = UIAlertController(title: "Bestellen?", message: nil, preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Bestellung aufgeben", style: .default)
let cancelAction = UIAlertAction(title: "Bestellung bearbeiten", style: .destructive)
alert.addAction(okayAction)
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
func calculateTotal() {
var totalProductCount = 0
var totalProductPrice = 0.00
for (_,value) in cartDictionary {
totalProductCount += value.counter
totalProductPrice += value.totalPrice
}
self.totalProductCountLabel.text = "Anzahl Produkte: \(totalProductCount) Stück"
self.totalProductPriceLabel.text = "Preis Produkte: \(String(format: "%5.2f", totalProductPrice)) €"
if cartDictionary.isEmpty {
print("Der Einkaufswagen ist leer")
timerEnabled = true
timerCount = 0
startTimer()
}
}
func startTimer() {
_ = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
if self.timerEnabled != false && self.timerCount < 4 {
self.productTableView.reloadData()
print("Die Produktliste wurde aktualisiert!")
self.timerCount += 1
} else {
timer.invalidate()
self.timerEnabled = true
self.timerCount = 0
}
}
}
// MARK: - TableView Delegate
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 188
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
timerEnabled = false
timerCount = 5
}
// MARK: - Navigation
static func fromStoryboard(_ storyboard: UIStoryboard = UIStoryboard(name: Constants.StoryboardID.main, bundle: nil),
forShop shop: Shop) -> NewOrderViewController {
let controller = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardID.newOrderViewController) as! NewOrderViewController
controller.shop = shop
return controller
}
}
All problems fixed by making a static instance of my NewOrderViewController!
class NewOrderViewController: UIViewController, UITableViewDelegate, CartSelection {
#IBOutlet var productTableView: UITableView!
#IBOutlet weak var totalProductCountLabel: UILabel!
#IBOutlet weak var totalProductPriceLabel: UILabel!
private var shop: Shop!
var totalProductCount = 0
var timerEnabled = true
var timerCount = 0
var cartDictionary = [String: Product]()
static var instance: NewOrderViewController?
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
NewOrderViewController.instance = self
productTableView.dataSource = dataSource
productTableView.delegate = self
query = baseQuery
productTableView.allowsSelection = false
productTableView.reloadData()
timerEnabled = true
timerCount = 0
totalProductCountLabel.text = "Anzahl Produkte: 0 Stück"
totalProductPriceLabel.text = "Preis Produkte: 0.00 €"
}

Passing data in PageViewControllers swift

I have a page controller where I added UIViewControllers and display a bunch of form in each viewcontroller. The issue I am facing now is that I need to get the data supplied in each of the forms and save it which is done in the last view controller. I have tried using delegates but the moment the next button is clicked, the previous value stored becomes nil and only the value of the latest VC is displayed. How can I pass data in this textfields. Any help is appritated.
My delegate
protocol NextDelegate: AnyObject {
func next(pageIndex: Int, model: CreatePropertyModel)
func previous(pageIndex: Int, model: CreatePropertyModel)
}
how I created array of VC
lazy var controllers: [UIViewController] = {
let descVC = DescVC()
descVC.delegate = self
let priceVC = PriceVC()
priceVC.delegate = self
let featuresVC = FeaturesVC()
featuresVC.delegate = self
let picturesVC = PicturesVC()
picturesVC.delegate = self
return [descVC, priceVC, featuresVC, picturesVC]
}()
Model Example
class CreatePropertyModel: DictionaryEncodable {
var title: String?
var desc: String?
var property_type_id: Int?
var property_sub_type_id: Int?
var location_id: Int?
var currency: String?
var price: Int?
}
For all your steps, store it in a singleton.
protocol Answer {
var isDone: Bool { get }
}
class Answer1: Answer {
static public let updatedNotification = Notification.Name("Answer1Updated")
var name: String? {
didSet {
NotificationCenter.default.post(name: Answer1.updatedNotification, object: nil)
}
}
var isDone: Bool {
return name != nil
}
}
class Answer2: Answer {
var age: Int?
var isDone: Bool {
return age != nil
}
}
class Form {
static let shared = Form()
var answers: [Answer] = [Answer1(), Answer2()]
var isDone: Bool {
return answers.allSatisfy { $0.isDone == true }
}
private init() {}
func reset() {
answers = [Answer1(), Answer2()]
}
var answer1: Answer1? {
return Form.shared.answers.filter { $0 is Answer1 }.first as? Answer1
}
var answer2: Answer2? {
return Form.shared.answers.filter { $0 is Answer2 }.first as? Answer2
}
}
Then, in your view controller, read / write values like this.
class MyViewControllerForAnswer1: UIViewController {
var answer: Answer1? {
return Form.shared.answer1
}
var name: String? {
get {
return answer?.name
}
set {
answer?.name = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(answerUpdated(notification:)), name: Answer1.updatedNotification, object: nil)
}
#objc func answerUpdated(notification: Notification) {
// Update your content
}
}

What is Causing these Memory Leaks in my Swift code?

Is there anyone who can tell my why my code is getting the following four memory leaks (as indicated below) and the code changes that need to be made to rectifiy it? It is a collectionNode I have created which has a dataSource and delegate and performing leak profiling indicates the four leaks.
weak var dataSource: CollectionNodeDataSource? { didSet{ setupData() } }
public func setupData() {
let numberOfItems = dataSource!.numberOfItems() //LEAK 4bytes
var j: Int = 0
for i in numberOfCells/2..<numberOfCells {
let item = dataSource!.collectionNode(self, itemFor: i) //LEAK 4bytes
item.index = i
item.id = j
array_cells.append(item)
j = j == numberOfItems-1 ? 0 : j+1
}
j = numberOfItems-1
if array_cells[0].nameLabel != nil { cellHeight = array_cells[0].nameLabel!.frame.size.height } //LEAK 25bytes
for i in (0...(numberOfCells/2)-1).reversed() {
let item = dataSource!.collectionNode(self, itemFor: i)
item.index = i
item.id = j
array_cells.insert(item, at: 0)
j = j == 0 ? numberOfItems-1 : j-1
cumultive_posY = cumultive_posY + cellHeight + spaceBetweenItems
}
for item in array_cells { itemNode?.addChild(item) } //LEAK 4bytes
if currentIndex == -1 {
currentIndex = numberOfCells/2
} else {
if let id = array_cells[currentIndex].id { currentIndex = (numberOfCells/2)+id }
}
biggestItem = array_cells.sorted{ $0.calculateAccumulatedFrame().size.height > $1.calculateAccumulatedFrame().size.height }.first! //LEAK 23b
setSpacing()
DispatchQueue.main.async { [weak self] in //LEAK 1 byte
self?.snap(to: self!.currentIndex, withDuration: 0)
self?.updateItemAppearance(aboutIndex: self!.currentIndex)
self?.itemNode?.isHidden = false
}
}
protocol CollectionNodeDataSource: class {
func numberOfItems() -> Int
func collectionNode(_ collection: CollectionNode, itemFor index: Index) -> CollectionNodeItem
}
The data model is as follows:
public class CapOptionsModel {
static var `default`: CapOptionsModel = CapOptionsModel()
let array_options = [ApetureCapSpinnerOption(mode: .liveStream, name: "Stream", image: #imageLiteral(resourceName: "ButtonTip_LiveStream")),
ApetureCapSpinnerOption(mode: .sessionCapture, name: "Capture", image:#imageLiteral(resourceName: "ButtonTip_SessionCapture"))]
private init() {
}
}
And the CollectionNodeItem is as like so:
class CollectionNodeItem: SKNode {
fileprivate weak var collection: CollectionNode? { return self.parent as? CollectionNode }
weak var nameLabel: SKLabelNode?
weak var imageNode: SKSpriteNode?
var id : Int!
var index : Index!
public override init() {
super.init()
isUserInteractionEnabled = true
let nameLabel = SKLabelNode(fontNamed: FONT_EUROEXT)
addChild(nameLabel)
self.nameLabel = nameLabel
let imageNode = SKSpriteNode()
addChild(imageNode)
self.imageNode = imageNode
}
required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

Swift 3 Protocol Oriented Programming results in random SIGBUS crashes

I am responsible of a complete Swift 3 application and one of the crashes that occurs regularly is a SIGBUS signal that I can't understand at all:
Thread 0 Crashed:
0 libswiftCore.dylib 0x00000001009b4ac8 0x1007b8000 +2083528
1 LeadingBoards #objc PageView.prepareForReuse() -> () (in LeadingBoards) (PageView.swift:0) +1114196
2 LeadingBoards specialized ReusableContentView<A where ...>.reuseOrInsertView(first : Int, last : Int) -> () (in LeadingBoards) (ReusableView.swift:101) +1730152
3 LeadingBoards DocumentViewerViewController.reuseOrInsertPages() -> () (in LeadingBoards) (DocumentViewerViewController.swift:0) +1036080
4 LeadingBoards specialized DocumentViewerViewController.scrollViewDidScroll(UIScrollView) -> () (in LeadingBoards) (DocumentViewerViewController.swift:652) +1089744
5 LeadingBoards #objc DocumentViewerViewController.scrollViewDidScroll(UIScrollView) -> () (in LeadingBoards) +1028252
6 UIKit 0x000000018c2a68d4 0x18bf85000 +3283156
7 UIKit 0x000000018bfb2c08 0x18bf85000 +187400
8 UIKit 0x000000018c143e5c 0x18bf85000 +1830492
9 UIKit 0x000000018c143b4c 0x18bf85000 +1829708
10 QuartzCore 0x00000001890755dc 0x18906b000 +42460
11 QuartzCore 0x000000018907548c 0x18906b000 +42124
12 IOKit 0x00000001860d7b9c 0x1860d2000 +23452
13 CoreFoundation 0x0000000185e01960 0x185d3e000 +801120
14 CoreFoundation 0x0000000185e19ae4 0x185d3e000 +899812
15 CoreFoundation 0x0000000185e19284 0x185d3e000 +897668
16 CoreFoundation 0x0000000185e16d98 0x185d3e000 +888216
17 CoreFoundation 0x0000000185d46da4 0x185d3e000 +36260
18 GraphicsServices 0x00000001877b0074 0x1877a4000 +49268
19 UIKit 0x000000018bffa058 0x18bf85000 +479320
20 LeadingBoards main (in LeadingBoards) (AppDelegate.swift:13) +77204
21 libdyld.dylib 0x0000000184d5559c 0x184d51000 +17820
The logic behind that is the logic for reusing views in a scrollview, as described by Apple in a WWDC video (can't find the year and the video...):
PageView is a class that implement ReusableView and Indexed:
class PageView: UIView {
enum Errors: Error {
case badConfiguration
case noImage
}
enum Resolution: String {
case high
case low
static var emptyGeneratingTracker: [PageView.Resolution: Set<String>] {
return [.high:Set(),
.low:Set()]
}
/// SHOULD NOT BE 0
var quality: CGFloat {
switch self {
case .high:
return 1
case .low:
return 0.3
}
}
var JPEGQuality: CGFloat {
switch self {
case .high:
return 0.8
case .low:
return 0.25
}
}
var atomicWrite: Bool {
switch self {
case .high:
return false
case .low:
return true
}
}
var interpolationQuality: CGInterpolationQuality {
switch self {
case .high:
return .high
case .low:
return .low
}
}
var dispatchQueue: OperationQueue {
switch self {
case .high:
return DocumentBridge.highResOperationQueue
case .low:
return DocumentBridge.lowResOperationQueue
}
}
}
#IBOutlet weak var imageView: UIImageView!
// Loading
#IBOutlet weak var loadingStackView: UIStackView!
#IBOutlet weak var pageNumberLabel: UILabel!
// Error
#IBOutlet weak var errorStackView: UIStackView!
// Zoom
#IBOutlet weak var zoomView: PageZoomView!
fileprivate weak var bridge: DocumentBridge?
var displaying: Resolution?
var pageNumber = 0
override func layoutSubviews() {
super.layoutSubviews()
refreshImageIfNeeded()
}
func configure(_ pageNumber: Int, zooming: Bool, bridge: DocumentBridge) throws {
if pageNumber > 0 && pageNumber <= bridge.numberOfPages {
self.bridge = bridge
self.pageNumber = pageNumber
self.zoomView.configure(bridge: bridge, pageNumber: pageNumber)
} else {
throw Errors.badConfiguration
}
NotificationCenter.default.addObserver(self, selector: #selector(self.pageRendered(_:)), name: .pageRendered, object: bridge)
NotificationCenter.default.addObserver(self, selector: #selector(self.pageFailedRendering(_:)), name: .pageFailedRendering, object: bridge)
pageNumberLabel.text = "PAGE".localized + " \(pageNumber)"
if displaying == nil {
loadingStackView.isHidden = false
errorStackView.isHidden = true
}
if displaying != .high {
refreshImage()
}
if zooming {
startZooming()
} else {
stopZooming()
}
}
fileprivate func isNotificationRelated(notification: Notification) -> Bool {
guard let userInfo = notification.userInfo else {
return false
}
guard pageNumber == userInfo[DocumentBridge.PageNotificationKey.PageNumber.rawValue] as? Int else {
return false
}
guard Int(round(bounds.width)) == userInfo[DocumentBridge.PageNotificationKey.Width.rawValue] as? Int else {
return false
}
guard userInfo[DocumentBridge.PageNotificationKey.Notes.rawValue] as? Bool == false else {
return false
}
return true
}
func pageRendered(_ notification: Notification) {
guard isNotificationRelated(notification: notification) else {
return
}
if displaying == nil || (displaying == .low && notification.userInfo?[DocumentBridge.PageNotificationKey.Resolution.rawValue] as? String == Resolution.high.rawValue) {
refreshImage()
}
}
func pageFailedRendering(_ notification: Notification) {
guard isNotificationRelated(notification: notification) else {
return
}
if displaying == nil {
imageView.image = nil
loadingStackView.isHidden = true
errorStackView.isHidden = false
}
}
func refreshImageIfNeeded() {
if displaying != .high {
refreshImage()
}
}
fileprivate func refreshImage() {
let pageNumber = self.pageNumber
let width = Int(round(bounds.width))
DispatchQueue.global(qos: .userInitiated).async(execute: { [weak self] () in
do {
try self?.setImage(pageNumber, width: width, resolution: .high)
} catch {
_ = try? self?.setImage(pageNumber, width: width, resolution: .low)
}
})
}
func setImage(_ pageNumber: Int, width: Int, resolution: Resolution) throws {
if let image = try self.bridge?.getImage(page: pageNumber, width: width, resolution: resolution) {
DispatchQueue.main.async(execute: { [weak self] () in
if pageNumber == self?.pageNumber {
self?.imageView?.image = image
self?.displaying = resolution
self?.loadingStackView.isHidden = true
self?.errorStackView.isHidden = true
}
})
} else {
throw Errors.noImage
}
}
}
extension PageView: ReusableView, Indexed {
static func instanciate() -> PageView {
return UINib(nibName: "PageView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! PageView
}
var index: Int {
return pageNumber
}
func hasBeenAddedToSuperview() { }
func willBeRemovedFromSuperview() { }
func prepareForReuse() {
NotificationCenter.default.removeObserver(self, name: .pageRendered, object: nil)
NotificationCenter.default.removeObserver(self, name: .pageFailedRendering, object: nil)
bridge = nil
imageView?.image = nil
displaying = nil
pageNumber = 0
zoomView?.prepareForReuse()
}
func prepareForRelease() { }
}
// MARK: - Zoom
extension PageView {
func startZooming() {
bringSubview(toFront: zoomView)
zoomView.isHidden = false
setNeedsDisplay()
}
func stopZooming() {
zoomView.isHidden = true
}
}
where ReusableView and Indexed are protocols defined that way :
protocol Indexed {
var index: Int { get }
}
protocol ReusableView {
associatedtype A
static func instanciate() -> A
func hasBeenAddedToSuperview()
func willBeRemovedFromSuperview()
func prepareForReuse()
func prepareForRelease()
}
// Make some func optionals
extension ReusableView {
func hasBeenAddedToSuperview() {}
func willBeRemovedFromSuperview() {}
func prepareForReuse() {}
func prepareForRelease() {}
}
ReusableContentView is a view that manage the view that are inserted, or reused. It's implemented depending of the containing view type :
class ReusableContentView<T: ReusableView>: UIView where T: UIView {
var visible = Set<T>()
var reusable = Set<T>()
...
}
extension ReusableContentView where T: Indexed {
/// To insert view using a range of ids
func reuseOrInsertView(first: Int, last: Int) {
// Removing no longer needed views
for view in visible {
if view.index < first || view.index > last {
reusable.insert(view)
view.willBeRemovedFromSuperview()
view.removeFromSuperview()
view.prepareForReuse()
}
}
// Removing reusable pages from visible pages array
visible.subtract(reusable)
// Add the missing views
for index in first...last {
if !visible.map({ $0.index }).contains(index) {
let view = dequeueReusableView() ?? T.instanciate() as! T // Getting a new page, dequeued or initialized
if configureViewWithIndex?(view, index) == true {
addSubview(view)
view.hasBeenAddedToSuperview()
visible.insert(view)
}
}
}
}
}
Witch is called by DocumentViewerViewController.reuseOrInsertPages(), triggered by scrollviewDidScroll delegate.
What can provoque my SIGBUS signal here? Is that the default implementation of func prepareForReuse() {} I use to make the protocol function optional? Any other ideas?
Of course, this crash is completly random and I wasn't able to reproduice it. I just receive crash reports about it from prod version of the app.
Thanks for your help !
For me it looks like something went wrong in PageView.prepareForReuse(). I'm not aware of the properties but from the prepareForReuse function it looks like your are accessing properties which maybe are #IBOutlets:
bridge = nil
imageView.image = nil
displaying = nil
pageNumber = 0
zoomView.prepareForReuse()
Could it be that imageView or zoomView are nil when you try to access them? If so, this could be the most simplistic fix:
func prepareForReuse() {
NotificationCenter.default.removeObserver(self, name: .pageRendered, object: nil)
NotificationCenter.default.removeObserver(self, name: .pageFailedRendering, object: nil)
bridge = nil
imageView?.image = nil
displaying = nil
pageNumber = 0
zoomView?.prepareForReuse()
}
Again, I am not sure about the implementation details of your PageView and I am only guessing this because it looks like you are instantiating it from a Nib and therefore my guess is you are using for example #IBOutlet weak var imageView: UIImageView!.
If for whatever reason this imageView becomes nil, accessing it will crash your app.

Resources