When the back button is pressed in my application, a delegate is called. All functions properly, however there is a large delay between the transition of the view. This gives the user a feeling that the app is crashed or isn't functioning properly.
The delegate methods that are triggered and causing the delay are shown here.
func setValues(obj: overviewProps){
overview = obj
print("Values Set")
surveyToEdit.overview = obj
updateSurveyObj()
}
func setArray(obj: [areaProps]) {
areas = obj
print("Set Array Sucessfully")
print(areas.count)
surveyToEdit.areas = obj
updateSurveyObj()
}
func saveSurveys(x : addedSurveys){
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(x, toFile: addedSurveys.ArchiveURL.path)
if isSuccessfulSave {
os_log("Surveys Saved", log: OSLog.default, type: .debug)
} else {
os_log("Failed to Save", log: OSLog.default, type: .error)
}
}
func updateSurveyObj(){
//this must be the issue
surveyToEdit.name = surveyToEdit.overview?.name ?? surveyToEdit.name
surveyToEdit.areas! = areas
surveyToEdit.overview! = overview
surveyListSurveys.surveys[surveyIndex] = surveyToEdit
saveSurveys(x: surveyListSurveys)
delegate?.saveInfo(updatedArray: surveyListSurveys)
print("the name is : \(surveyToEdit.overview!.name)")
}
When connected to the debugger, the device is showing a high memory usage and high cpu usage during this delay.
Here is the entire view controller for the project overview:
import UIKit
import os.log
class projectOverview: UIViewController, overviewDelegate, AreaDelegate {
var surveyToEdit = survey(areas: nil, settings: nil, overview: nil, name: "New Survey")
let IMG = UIImage(named: "noImage.png")
var areas = [areaProps(x: "New Area", y: "", z: "", a: "", b: "", p1: nil, p2: nil, p3: nil), areaProps(x: "New Area", y: "", z: "", a: "", b: "", p1: nil, p2: nil, p3: nil)]
var settings = companySettings(manager: "")
var overview = overviewProps(x: "", y: "", a: "", b: "", c: "", d: "", e: "", f: "", st: "", ct: "", stt: "")
var projectTitle: String = ""
let areaView = areaDetails()
let surveyHomeScreen = surveyList()
var surveyIndex = 0
var surveyListSurveys = addedSurveys(survey: [survey]())
var delegate:projectDelegate?
// need this to eventually pass back the data in the the survey object
func saveSurveys(x : addedSurveys){
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(x, toFile: addedSurveys.ArchiveURL.path)
if isSuccessfulSave {
os_log("Surveys Saved", log: OSLog.default, type: .debug)
} else {
os_log("Failed to Save", log: OSLog.default, type: .error)
}
}
func setProjDetails(){
print("gay")
if surveyToEdit.areas != nil{
areas = surveyToEdit.areas!
print("There is area")
}
if surveyToEdit.overview != nil{
overview = surveyToEdit.overview!
print("there is overview")
}
}
func updateSurveyObj(){
//this must be the issue
surveyToEdit.name = surveyToEdit.overview?.name ?? surveyToEdit.name
surveyToEdit.areas! = areas
surveyToEdit.overview! = overview
surveyListSurveys.surveys[surveyIndex] = surveyToEdit
saveSurveys(x: surveyListSurveys)
delegate?.saveInfo(updatedArray: surveyListSurveys)
print("the name is : \(surveyToEdit.overview!.name)")
}
let overviewController = addOverview()
override func viewDidLoad() {
super.viewDidLoad()
//areas = surveyToEdit.areas!
//overview = surveyToEdit.overview!
// Do any additional setup after loading the view.
//overviewController.delegate = self
//areaView.delegate = self
}
// MARK : - Actions
#IBAction func showPDF(_ sender: Any) {
performSegue(withIdentifier: "goToShare", sender: self)
print(overview.name)
}
#IBAction func editAreas(_ sender: Any) {
performSegue(withIdentifier: "goToServiceArea", sender: self)
}
#IBAction func editOverView(_ sender: Any) {
performSegue(withIdentifier: "goToMainDetails", sender: self)
}
func setValues(obj: overviewProps){
overview = obj
print("Values Set")
surveyToEdit.overview = obj
updateSurveyObj()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToMainDetails"{
let vc = segue.destination as! addOverview
vc.overview = overview
vc.delegate = self
} else if segue.identifier == "goToServiceArea"{ // this is the segue
let vc = segue.destination as! areaDetails
vc.areaArray = self.areas
vc.delegate = self
print(" \(areas.count) Areas in Array ") // the array contains only 1 object
} else {
let vc = segue.destination as! shareReport
vc.surveyToDisplay = surveyToEdit
}
}
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent:parent)
if parent == nil {
updateSurveyObj()
}
}
func setArray(obj: [areaProps]) {
areas = obj
print("Set Array Sucessfully")
print(areas.count)
surveyToEdit.areas = obj
updateSurveyObj()
}
#IBOutlet var floorPlanIMG: UIImageView!
and here is the view that the back button is being pressed on:
import UIKit
class addOverview: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var overview = overviewProps(x: "", y: "", a: "", b: "", c: "", d: "", e: "", f: "", st: "", ct: "", stt: "")
// MARK: - Init all UIVariables
let buildingTypes = ["Industrial", "School", "Office", "Multi-Tenant", "Church", "Manufacturing", "Misc"]
let cleaningFreqs = ["1/Week", "2/Week", "3/Week", "4/Week", "5/Week", "6/Week", "7/Week", "1/EOW", "1/Month"]
#IBOutlet var datePicker: UIDatePicker!
#IBOutlet var buildingName: UITextField!
#IBOutlet var street: UITextField!
#IBOutlet var city: UITextField!
#IBOutlet var State: UITextField!
#IBOutlet var contactName: UITextField!
#IBOutlet var phoneNumber: UITextField!
#IBOutlet var email: UITextField!
#IBOutlet var buildingPicker: UIPickerView!
#IBOutlet var cleaningFreqPicker: UIPickerView!
#IBOutlet var sqFt: UITextField!
var delegate:overviewDelegate?
// MARK: - Conform to PickerView
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView.tag == 2
{
return buildingTypes.count
}
else{
return cleaningFreqs.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if (pickerView.tag == 2)
{
return buildingTypes[row]
}else{
return cleaningFreqs[row]
}
}
override func viewDidLoad() {
super.viewDidLoad()
updateView()
buildingPicker.delegate = self
cleaningFreqPicker.delegate = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
if overview.building != ""{
buildingPicker.selectRow(buildingTypes.index(of: overview.building)!, inComponent: 0, animated: false)
cleaningFreqPicker.selectRow(cleaningFreqs.index(of: overview.freq)!, inComponent: 0, animated: false)
}
}
// set all the values of the object
func updateView(){
buildingName.text = overview.name
city.text = overview.city
contactName.text = overview.contact
email.text = overview.email
phoneNumber.text = overview.phone
sqFt.text = overview.sft
State.text = overview.state
street.text = overview.street
}
func setValues(){
overview.name = buildingName.text!
overview.city = city.text!
overview.contact = contactName.text!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
overview.date = dateFormatter.string(from: datePicker.date)
overview.email = email.text!
overview.building = buildingTypes[buildingPicker.selectedRow(inComponent: 0)]
overview.freq = cleaningFreqs[cleaningFreqPicker.selectedRow(inComponent: 0)]
overview.phone = phoneNumber.text!
overview.sft = sqFt.text!
overview.state = State.text!
overview.street = street.text!
}
// MARK: - Navigation
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent:parent)
if parent == nil {
setValues()
print("Back Button Pressed")
delegate?.setValues(obj: overview)
}
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
}
Related
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 €"
}
I'm currently trying to develop a prototype for a quiz application.
The questions for the quiz are read out of a JSON file and the answers are generated within the program. Those two parts are then matched to each other within a Mediator class.
Displaying the question in my ViewController works perfectly fine for the first question. When trying to retrieve the second question the array that stores the question in the Mediator class is empty. That obviously is the case, because I'm re-entering the Mediator after going to my ViewController.
How can I store the data more efficiently?
If you need more information, please let me know. I'm really grateful for every suggestion I can get. I'm totally stuck at right now!
ViewController:
[...]
override func viewDidLoad() {
super.viewDidLoad()
setup(categoryType)
navigationBar.title = barTitle
QAMatcher.delegate = self
QAMatcher.getCategory(categoryType)
}
#objc func didChooseAnswer(_ sender: UIButton){
QAMatcher.nextQuestion()
updateUI()
}
func updateUI(){
if let label = questionLabel {
label.text = QAMatcher.getQuestion()
}
if let type = AnswerType(rawValue: QAMatcher.getAnswerType()) {
addAnswerSection(type)
}
}
func addAnswerSection(_ type: AnswerType) {
if let stackView = quizStackView, let answerSection = uiBuilder.getAnswerSection(answerType: type) {
stackView.addArrangedSubview(answerSection)
}
}
[...]
Mediator Class:
import Foundation
protocol Mediator {
func sendRequest (_ request: Request, sender: Sender)
}
protocol MediatorDelegate {
func didUpdate(mediator: QuestionAnswerMediator, _ array: [QuestionData])
}
enum Sender {
case answer
case question
}
class QuestionAnswerMediator: Mediator {
var delegate: MediatorDelegate?
var questionArray = [QuestionData]()
var answerArray = [String]()
var request = Request()
var questionNum = 0
func getCategory(_ category: CategoryType) {
request.categoryType = category
matchQuestionAndAnswers()
}
func sendRequest (_ request: Request, sender: Sender){
if sender == .question {
let array = QuestionManager.shared.send(category: request.categoryType!)
questionArray.append(contentsOf: array)
}
else if sender == .answer {
let array = AnswerManager.shared.send(request: request)
if array?.isEmpty == false {
answerArray = []
answerArray.append(contentsOf: array ?? ["default"])
}
}
}
func fetchAnswers(for type: String, question: String) {
request.answerType = AnswerType(rawValue: type)
request.question = question
sendRequest(request, sender: .answer)
}
func fetchQuestions (for category: CategoryType){
request.categoryType = category
sendRequest(request, sender: .question)
}
func matchQuestionAndAnswers(){
var i = 0
fetchQuestions(for: request.categoryType!)
for question in questionArray {
fetchAnswers(for: question.answerType, question: question.question)
if answerArray.isEmpty != true {
questionArray[i].answers = answerArray
}
i += 1
}
self.delegate?.didUpdate(mediator: self, questionArray)
}
func getQuestion() -> String {
return questionArray[questionNum].question
}
func getAnswers() -> [String] {
return questionArray[questionNum].answers!
}
func getAnswerType() -> String {
return questionArray[questionNum].answerType
}
func nextQuestion () {
if questionNum + 1 < questionArray.count {
questionNum += 1
}
else {
questionNum = 0
}
}
}
ok, that was hard ;) i had to create my own project file, because yours was still missing. After doing that i tried to compile and run...but you did not fill the assets so it crashed. After faking your assets...it runs. ;)
You already analyzed your problem correctly. You should never create a viewcontroller mutliple times if you just call him once. So i deleted this and you now only have one instance.
i routed the "didchooseAnswer" through your classes...but you still have some work to do: i had to give a frame to your view class, so fill that frame you need, i don't know what frame you need.
I just corrected it for MultipleChoiceView...you have to do it for the other views as well like PolarView etc...
i hope i have everything copied what i changed, if not, just tell me. but maybe then you should give me rights on your github project so that i can commit it there...
Quizviewcontroller:
import UIKit
import CoreLocation
import os
class QuizViewController: UIViewController {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var navigationBar: UINavigationItem!
#IBOutlet var mainView: UIView!
#IBOutlet weak var quizStackView: UIStackView!
var barTitle: String?
//variables for location services
var locationManager = CLLocationManager()
var locationData: LocationModel?
//variables for communication with Mediator
var QAMatcher = QuestionAnswerMediator()
var questions : [QuestionData]?
var categoryType: CategoryType!
var timer = Timer()
//Change UI depending on answer type
var uiBuilder = AnswerViewBuilder()
override func viewDidLoad() {
super.viewDidLoad()
setup(categoryType)
navigationBar.title = barTitle
QAMatcher.delegate = self
QAMatcher.getCategory(categoryType)
}
#objc func didChooseAnswer(_ sender: UIButton){
QAMatcher.nextQuestion()
updateUI()
}
}
//MARK: - Setup & updating UI
extension QuizViewController: MediatorDelegate {
func didUpdate(mediator: QuestionAnswerMediator, _: [QuestionData]) {
self.updateUI()
}
func setup(_ category: CategoryType?) {
switch category {
case .shortterm:
print("No function yet")
case .longterm:
print("No function yet")
case .problemsolving:
print("No function yet")
case .orientation:
locationManager.delegate = self
checkLocationServiceSupport()
locationManager.requestWhenInUseAuthorization()
gecodeCurrentLocation()
default:
let message: StaticString = M.Errors.invalidCategoryError
os_log(message, log: OSLog.default, type: .error)
}
}
func updateUI(){
if let label = questionLabel {
label.text = QAMatcher.getQuestion()
}
if let type = AnswerType(rawValue: QAMatcher.getAnswerType()) {
addAnswerSection(type)
}
}
func addAnswerSection(_ type: AnswerType) {
if let stackView = quizStackView, let answerSection = uiBuilder.getAnswerSection(answerType: type, didChooseAnswer: self.didChooseAnswer(_:)) {
stackView.addArrangedSubview(answerSection)
}
}
}
//MARK: - CLLocationManagerDelegate
extension QuizViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
let message: StaticString = M.Errors.locationDetectionError
os_log(message, log: OSLog.default, type: .error)
}
func checkLocationServiceSupport () {
if CLLocationManager.significantLocationChangeMonitoringAvailable() != true {
let alert = UIAlertController(title: M.Alters.locationAlertTitle, message: M.Alters.locationAlert, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
NSLog("The user acknowledge that location services are not available.")
}))
self.present(alert, animated: true, completion: nil)
self.dismiss(animated: true, completion: nil)
}
}
func gecodeCurrentLocation() {
locationManager.requestLocation()
if let lastLocation = self.locationManager.location {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(lastLocation) { (placemarks, error) in
if error == nil {
if let placemark = placemarks?.first {
let city = placemark.locality!
let country = placemark.country!
self.locationData = LocationModel(city: city,country: country)
}
}
else {
let message: StaticString = M.Errors.locationDecodingError
os_log(message, log: OSLog.default, type: .error)
}
}
}
}
}
//MARK: - UITextFieldDelegate
//extension CategoryViewController: UITextFieldDelegate {
//
// func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
// if textField.text != "" {
// return true
// }
// else {
// textField.placeholder = "Enter your answer here"
// return false
// }
// }
//
// func textFieldDidEndEditing(_ textField: UITextField) {
// }
//
// func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// textField.endEditing(true)
// return true
// }
//}
MulitpleChoiceView
import UIKit
class MultipleChoiceView: UIStackView {
var button1 = UIButton()
var button2 = UIButton()
var button3 = UIButton()
var didChooseAnswer : ((UIButton)->())?
init(frame: CGRect, didChooseAnswer: #escaping (UIButton)->()) {
super.init(frame: frame)
self.didChooseAnswer = didChooseAnswer
self.addAnswerView()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addAnswerView() {
setupStackView()
costumeButtons()
}
func setupStackView() {
self.alignment = .fill
self.distribution = .fillEqually
self.spacing = 10
self.axis = .vertical
}
func costumeButtons() {
let buttons = [button1 , button2, button3]
let dimensions = CGRect(x: 0, y: 0, width: 120, height: 20)
let color = #colorLiteral(red: 0.7294117647, green: 0.7450980392, blue: 1, alpha: 1)
for button in buttons {
button.frame = dimensions
button.backgroundColor = color
button.layer.cornerRadius = button.frame.size.height / 4
button.isUserInteractionEnabled = true
button.addTarget(self, action: M.Selector.buttonAction, for: .touchUpInside)
self.addArrangedSubview(button)
}
}
#objc func didChooseAnswer(_ sender: UIButton) {
if let didChooseAnswer = didChooseAnswer {
didChooseAnswer(sender)
}
}
}
AnswerViewBuilder
import UIKit
class AnswerViewBuilder {
func getAnswerSection(answerType: AnswerType, didChooseAnswer: #escaping (UIButton)->()) -> UIView?{
switch (answerType) {
case .multipleChoice:
return MultipleChoiceView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), didChooseAnswer: didChooseAnswer)
case .textField:
return TextFieldView()
case .polarQuestion:
return PolarQuestionView()
case .ranking:
return MultipleChoiceView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), didChooseAnswer: didChooseAnswer)
default:
print("Answer section could not be created.")
return nil
}
}
}
I am a beginner to Xcode and Swift and I am currently creating an application where the user adds a person on the application and after that it right the amount of money they owe that person or that person owes him/her.
Note: I have used core data to store all the value
I actually want to change the value of a variable when switch is on and off. For instance, in the following I want the "amount" to be negative when the switch is on and positive when it is off. Also, when I try to do this and send amount variable to previous view controller I can't send the value depending on the UISwitch because it always shows positive. I am trying to find a solution to this from past 3 days therefore can you please help me? Thanks a lot in advance
Owe ViewController
import UIKit
class NewOweTableViewController: UIViewController {
#IBOutlet weak var titleTextField: UITextField!
#IBOutlet weak var locationTextField: UITextField!
#IBOutlet weak var amountTextField: UITextField!
#IBOutlet weak var datePicker: UIDatePicker!
let context = (UIApplication.shared.delegate as!
AppDelegate).persistentContainer.viewContext
var owe: Owe?
var dataInfo: [Owe] = []
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.viewWithTag(1)?.isHidden = true
let saveBTN = UIBarButtonItem(barButtonSystemItem:UIBarButtonSystemItem.save, target:self,
action: #selector(saveButtonTapped(_:)))
let deleteBTN = UIBarButtonItem(barButtonSystemItem:UIBarButtonSystemItem.trash, target:self,
action: #selector(deleteButtonTapped(_:)))
self.navigationItem.rightBarButtonItems = [saveBTN, deleteBTN]
if !dataInfo.isEmpty {
titleTextField.text = dataInfo[0].name
amountTextField.text = (NSString(format: "%.2f", (dataInfo[0].amount) as CVarArg) as String)
locationTextField.text = dataInfo[0].location
datePicker.date = dataInfo[0].date!
}
}
#objc func saveButtonTapped(_ sender: UIButton){
if !dataInfo.isEmpty{
let data = dataInfo[0]
data.name = titleTextField.text
data.amount = Double(amountTextField.text!)!
data.location = locationTextField.text
data.date = datePicker.date
}
else if titleTextField.text == "" || amountTextField.text == "" || locationTextField.text == "" {
return
}
else{
let data = Owe(context: context)
data.name = titleTextField.text
data.amount = Double(amountTextField.text!)!
data.location = locationTextField.text
data.date = datePicker.date
}
do {
try context.save()
navigationController?.popViewController(animated: true)
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
#objc func deleteButtonTapped(_ sender: UIButton){
if !dataInfo.isEmpty{
let data = dataInfo[0]
context.delete(data)
do {
try context.save()
navigationController?.popViewController(animated: true)
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
#IBAction func oweSwitch(_ sender: UISwitch) {
if sender.isOn {
owe?.amount = (owe?.amount)! * (-1)
amountTextField.textColor = UIColor.green
} else {
owe?.amount = (owe?.amount)! * (1)
amountTextField.textColor = UIColor.red
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Previous View Controller
import UIKit
class PersonDetailTableViewController: UITableViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var totalLabel: UILabel?
var person: People?
var owe: Owe?
#IBOutlet var personTable: UITableView!
var dataInfo: [Owe] = []
var selectedObject: [Owe] = []
var balanceAmount = "Balance: "
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (dataInfo.count)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = personTable
.dequeueReusableCell(withIdentifier: "detailsCell", for: indexPath)
cell.textLabel?.text = dataInfo[indexPath.row].name
cell.detailTextLabel?.text = "₹ \(dataInfo[indexPath.row].amount)"
if dataInfo[indexPath.row].amount < 0 {
cell.detailTextLabel?.textColor = UIColor.red
} else {
cell.detailTextLabel?.textColor = UIColor.green
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedObject = [dataInfo[indexPath.row]]
performSegue(withIdentifier: "addOweDetails", sender: nil)
tableView.deselectRow(at: indexPath, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
personTable.dataSource = self
addTotalToNav()
print(dataInfo as Any)
}
// MARK: - Table view data source
func addTotalToNav() -> Void {
if let navigationBar = self.navigationController?.navigationBar {
let totalFrame = CGRect(x: 10, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
totalLabel = UILabel(frame: totalFrame)
totalLabel?.text = balanceAmount
totalLabel?.tag = 1
totalLabel?.font = UIFont.boldSystemFont(ofSize: 14)
totalLabel?.textColor = UIColor.red
// navigationBar.large = totalLabel?.text
self.title = totalLabel?.text
}
}
func getData() -> Void {
do{
dataInfo = try context.fetch(Owe.fetchRequest())
var total:Double = 0.00
for i in 0 ..< dataInfo.count {
total += dataInfo[i].amount as! Double
}
balanceAmount = "Balance: ₹" + (NSString(format: "%.2f", total as CVarArg) as String)
}
catch{
print("Fetching Failed")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! NewOweTableViewController
vc.dataInfo = selectedObject
selectedObject.removeAll()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getData()
personTable.reloadData()
if (self.navigationController?.navigationBar.viewWithTag(1)?.isHidden == true){
self.navigationController?.navigationBar.viewWithTag(1)?.removeFromSuperview()
addTotalToNav()
}
}
}
Core Data for owe
import UIKit
import CoreData
#objc(Owe)
public class Owe: NSManagedObject {
var date: Date? {
get{
return rawDate as Date?
}
set {
rawDate = newValue as NSDate?
}
}
convenience init?(name: String?, location: String?, amount: Double, date: Date?) {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
guard let context = appDelegate?.persistentContainer.viewContext
else {
return nil
}
self.init(entity: Owe.entity(), insertInto: context)
self.name = name
self.location = location
self.amount = amount
self.date = date
}
}
Hi there and welcome to the Swift Community!
If I understand correctly, you are trying to propagates updates backwards from NewOweTableViewController to PersonDetailTableViewController. If that is the case, an easy way to achieve this with your MVC architecture is by passing a closure to NewOweTableViewController when you initialize it in PersonDetailTableViewController.
In order to do so,
Update NewOweTableViewController and add a closure property.
class NewOweTableViewController: UIViewController {
// ...
var switchValueUpdate: ((Bool) -> ())?
// ...
}
Make sure you call this closure inside your #IBAction that you link to your switch in NewOweTableViewController
#IBAction func oweSwitch(_ sender: UISwitch) {
if sender.isOn {
owe?.amount = (owe?.amount)! * (-1)
amountTextField.textColor = UIColor.green
} else {
owe?.amount = (owe?.amount)! * (1)
amountTextField.textColor = UIColor.red
}
switchValueUpdate?(sender.isOn)
}
update PersonDetailTableViewController to set the closure
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! NewOweTableViewController
vc.dataInfo = selectedObject
selectedObject.removeAll()
vc.switchValueUpdate = { (isOn) in
// Here you go, update PersonDetailTableViewController to reflect changes related to the switch!
}
}
That's it! Let me know if you have any question on that code, hope it helps!
How can I update or insert a row in a tableview without reloading all the data?
In the ClientsViewController a list of clients is shown alphabetically and separated by sections (first letter of the client`s name).
When I update a client, the tableview shows 2 entries (old and new one) of the same client. When I try to add a client, it crashes.
I think the problem is with indexing.
class ClientsViewController: UITableViewController {
var sortedFirstLetters: [String] = []
var sections: [[Client]] = [[]]
var tableArray = [Client]()
var client: Client?
var refresher: UIRefreshControl!
#IBOutlet var noClientsView: UIView!
#IBAction func unwindToClients(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ClientViewController, let client = sourceViewController.client {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing client.
tableArray[selectedIndexPath.row] = client
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
}
else {
// Add a client.
let newIndexPath = IndexPath(row: tableArray.count, section: 0)
tableArray.append(client)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondScene = segue.destination as! ClientViewController
if segue.identifier == "ShowDetail", let indexPath = self.tableView.indexPathForSelectedRow {
let currentPhoto = sections[indexPath.section][indexPath.row]
secondScene.client = currentPhoto
}
else if segue.identifier == "AddItem" {
print("add")
}
else {
fatalError("The selected cell is not being displayed by the table")
}
}
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
getClients()
refreshControl.endRefreshing()
}
}
extension ClientsViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: #selector(ClientsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
tableView.backgroundView = noClientsView
getClients() //for only the 1st time ==> when view is created ==> ok ish
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
getClients() // not a good idea to make a request to the server everytime the view appears on the screen.
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(self.tableArray.count > 0) {
return sortedFirstLetters[section]
}
else {
return ""
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
print(sortedFirstLetters)
return sortedFirstLetters
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let name = sections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ClientCell", for: indexPath)
cell.textLabel?.text = name.name
cell.detailTextLabel?.text = name.city + " - " + name.province
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(sections[section].count > 0) {
tableView.backgroundView = nil
}
return sections[section].count
}
func getClients() {
makeRequest(endpoint: "api/clients/all",
parameters: [:],
completionHandler: { (container : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /getClients")
print(error)
return
}
self.tableArray = (container?.result)!
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
} )
}
//sorts and makes the index
func prepareData() {
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
}
}
Bellow is ClientViewController and Client struct.
class ClientViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var addressTextField: UITextField!
#IBOutlet weak var cityTextField: UITextField!
#IBOutlet weak var provinceTextField: UITextField!
#IBOutlet weak var postalCodeTextField: UITextField!
#IBOutlet weak var contactsLabel: UILabel!
let numberOfRowsAtSection: [Int] = [4, 2]
var client: Client?
var selectedProvince: String?
override func viewWillAppear(_ animated: Bool) {
self.title = "New"
if (client?.client_id) != nil {
self.title = "Edit"
nameTextField.text = client?.name
provinceTextField.text = client?.province
cityTextField.text = client?.city
addressTextField.text = client?.address
postalCodeTextField.text = client?.postal_code
selectedProvince = client?.province
}
}
#objc func save(sender: UIButton!) {
let name = nameTextField.text ?? ""
let address = addressTextField.text ?? ""
let city = cityTextField.text ?? ""
let province = selectedProvince ?? ""
let postal_code = postalCodeTextField.text ?? ""
var endPoint: String
if (client?.client_id) != nil {
endPoint = "api/clients/update"
} else {
endPoint = "api/clients/add"
}
client = Client(name:name, client_id: client?.client_id, postal_code: postal_code, province: province, city: city, address: address)
let requestBody = makeJSONData(client)
makeRequestPost(endpoint: endPoint,
requestType: "POST",
requestBody: requestBody,
view: view,
completionHandler: { (response : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /todos")
print(error)
return
}
let b = (response?.meta)!
let a = (response?.result[0])
let client_id = a?.client_id
self.client?.client_id = client_id
if(b.sucess == "yes") {
//change message and use the custom func like on error.
let alert = UIAlertController(title: "Success!", message: "All good", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {
(_)in
self.performSegue(withIdentifier: "unwindToClients", sender: self)
})
alert.addAction(OKAction)
DispatchQueue.main.async(execute: {
self.present(alert, animated: true, completion: nil)
})
}
else
{
self.showAlert(title: "Error", message: "Error Creating Client")
//return
}
} )
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionHeaderHeight = 50.0;
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(save))
let thePicker = UIPickerView()
provinceTextField.inputView = thePicker
thePicker.delegate = self
// ToolBar
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
toolBar.sizeToFit()
// Adding Button ToolBar
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(ClientDetailViewController.doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ClientDetailViewController.cancelClick))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
provinceTextField.inputAccessoryView = toolBar
}
#objc func doneClick() {
provinceTextField.resignFirstResponder()
}
#objc func cancelClick() {
provinceTextField.resignFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows: Int = 0
if section < numberOfRowsAtSection.count {
rows = numberOfRowsAtSection[section]
}
return rows
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let backItem = UIBarButtonItem()
backItem.title = "Client"
navigationItem.backBarButtonItem = backItem
if segue.identifier == "unwindToClients",
let destination = segue.destination as? ClientsViewController
{
destination.client = client
}
if segue.identifier == "showContacts",
let destination = segue.destination as? ContactsViewController
{
destination.client = client
}
}
// MARK: - Picker view
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView( _ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return provinces.count
}
func pickerView( _ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return provinces[row].name
}
func pickerView( _ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
provinceTextField.text = provinces[row].name
}
}
struct Client: Codable {
var client_id: Int!
let name: String!
let postal_code: String!
let province: String!
let city: String!
let address: String!
init(name: String, client_id: Int! = nil, postal_code: String, province: String, city: String, address: String) {
self.client_id = client_id
self.name = name
self.postal_code = postal_code
self.province = province
self.city = city
self.address = address
}
var nameFirstLetter: String {
return String(self.name[self.name.startIndex]).uppercased()
}
}
Append the items in datasource at index where you want to insert row in table. than follow steps 2,3,4.
Call method tbl.beginupdate
call insertRowsAtIndexPaths method of table view
Call tbl.endupdate
you can update tableView rows using this..
self.tableView.reloadRows(at: [indexPath!], with: .top)
I have a NSMutableArray and I'm trying to create a search bar. I have been looking for hours and I can't find anything that really helps me because I'm using the NSMutableArray with Firebase. I'm kinda new to Swift. I have created a table view and a search bar. The table view is showing data from the NSMutableArray. How can I make it so when the user is searching on the search bar then the whole post comes up?
If there is a post that contains 2 Strings and an image and one String is "A City" and the other String is the title of the post, and let's say that when the user is searching only for the title of the post or only the city, then the entire post shows. If the result could be on the same viewcontroller like on the same tableview.
I have written this so far in my viewDidLoad:
func setUpSearchBar() {
let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
searchController.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
}
And my array is like this:
var posts = NSMutableArray()
The only thing really that I have written is so that the search bar is setup but not really functional because I have not found anything that could help me and since I'm new to this is not either sure how to make the search bar work at all. All the tutorials I have to find so far is very normal arrays like:
var post = ["pear", "orange", "apple"]
But I cant find on how to make the search iterate through my post NSMutableArray.
I would be really happy if anyone could possibly help me through this. I don't want straight answers if possible with some explanation so I can learn this and create this myself.
this is the code that downloads the data and displays it in the TableView
import UIKit
import FirebaseStorage
import FirebaseDatabase
import FirebaseAuth
import FirebaseCore
import Firebase
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var postsTableView: UITableView!
var posts = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchBar()
loadData()
self.postsTableView.delegate = self
self.postsTableView.dataSource = self
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadData() {
Database.database().reference().child("posts").queryOrdered(byChild: "timeorder").observeSingleEvent(of: .value) { (snapshot) in
if let postsDictionary = snapshot.value as? [String: AnyObject] {
for post in postsDictionary {
self.posts.add(post.value)
}
self.postsTableView.reloadData()
}
}
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PostTableViewCell
// Configure the cell...
let post = self.posts[indexPath.row] as! [String: AnyObject]
cell.titleLabel.text = post["title"] as? String
cell.contentTextView.text = post["content"] as? String
cell.dateAndTimeLabel.text = post["time"] as? String
cell.usernameLabel.text = post["username"] as? String
cell.locationAdressLabel.text = post["adress"] as? String
if let imageName = post["image"] as? String {
let imageRef = Storage.storage().reference().child("images/\(imageName)")
imageRef.getData(maxSize: 25 * 1024 * 1024) { (data, error) -> Void in
if error == nil {
//successfull
let downloadedImage = UIImage(data: data!)
cell.postsImageView.image = downloadedImage
}else {
// error
print("there was an error downloading image: \(String(describing: error?.localizedDescription))")
}
}
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 500.0
}
#IBAction func goToProfilePage(_ sender: Any) {
let logoutSuccess = self.storyboard?.instantiateViewController(withIdentifier: "ProfileVC")
self.present(logoutSuccess!, animated: true, completion: nil)
}
#IBAction func navigateButton(_ sender: Any) {
let navigate = self.storyboard?.instantiateViewController(withIdentifier: "NavigateVC")
self.present(navigate!, animated: true, completion: nil)
}
#IBAction func chooseCountry(_ sender: Any) {
let navigate = self.storyboard?.instantiateViewController(withIdentifier: "CountryVC")
self.present(navigate!, animated: true, completion: nil)
}
func setUpSearchBar() {
let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
searchController.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
}
And Here is The Code That uploads the post to Firebase
import UIKit
import FirebaseDatabase
import FirebaseStorage
import FirebaseAuth
import Firebase
class PostViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
#IBOutlet weak var addImageButton: UIBarButtonItem!
#IBOutlet weak var pickCountryPicker: UIPickerView!
#IBOutlet weak var choosenCountryLabel: UILabel!
#IBOutlet weak var titleTextField: UITextField!
#IBOutlet weak var contentTextView: UITextView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var locationAdressTextField: UITextField!
var imageFileName = ""
var timeStamps = ""
var secondTimeStamps = ""
override func viewDidLoad() {
super.viewDidLoad()
let currentDateTime = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
timeStamps = "\(dateFormatter.string(from: currentDateTime))"
let secondDateFormatter = DateFormatter()
secondDateFormatter.dateFormat = "yyyy-MM-dd HH:00:00"
secondTimeStamps = "\(secondDateFormatter.string(from: currentDateTime))"
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func postButton(_ sender: Any) {
if (self.imageFileName != "") {
if choosenCountryLabel.text == "Afghanistan" {
// image has finshed the uploading, Saving Post!!!
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if let userDictionary = snapshot.value as? [String: AnyObject] {
for user in userDictionary{
if let username = user.value as? String {
if let streetAdress = self.locationAdressTextField.text {
if let title = self.titleTextField.text {
if let content = self.contentTextView.text {
let postObject: Dictionary<String, Any> = [
"uid" : uid,
"title" : title,
"content" : content,
"username" : username,
"time" : self.timeStamps,
"timeorder" : self.secondTimeStamps,
"image" : self.imageFileName,
"adress" : streetAdress
]
Database.database().reference().child("posts").childByAutoId().setValue(postObject)
Database.database().reference().child("Afghanistanposts").childByAutoId().setValue(postObject)
Database.database().reference().child(uid).childByAutoId().setValue(postObject)
let alertPosting = UIAlertController(title: "Successfull upload", message: "Your acty was successfully uploaded.", preferredStyle: .alert)
alertPosting.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
let vc = self.storyboard?.instantiateViewController(withIdentifier: "AfghanistanVC")
self.present(vc!, animated: true, completion: nil)
}))
self.present(alertPosting, animated: true, completion: nil)
print("Posted Succesfully to Firebase, Saving Post!!!")
}
}
}
}
}
}
})
}
}
}else{
let alertNotPosting = UIAlertController(title: "Seems like you got connection problems", message: "Your image has not been uploaded. Please Wait 10 seconds and try again.", preferredStyle: .alert)
alertNotPosting.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alertNotPosting, animated: true, completion: nil)
}
#IBAction func addImage(_ sender: Any) {
if addImageButton.isEnabled == false {
let alertNotPosting = UIAlertController(title: "Image already Picked", message: "sorry you already have an image picked", preferredStyle: .alert)
alertNotPosting.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alertNotPosting, animated: true, completion: nil)
}else {
let picker = UIImagePickerController()
picker.delegate = self
self.present(picker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
self.imageView.image = pickedImage
uploadImage(image: pickedImage)
picker.dismiss(animated: true, completion: nil)
addImageButton.isEnabled = false
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
func uploadImage(image: UIImage){
let randomName = randomStringWithLength(length: 10)
let imageData = UIImageJPEGRepresentation(image, 1.0)
let uploadRef = Storage.storage().reference().child("images/\(randomName).jpg")
let uploadTask = uploadRef.putData(imageData!, metadata: nil) { metadata, error in
if error == nil {
//success
print("SuccessFully uploaded")
self.imageFileName = "\(randomName as String).jpg"
}else {
// not success = error
print("error with the Image: \(String(describing: error?.localizedDescription))")
}
}
}
func randomStringWithLength(length: Int) -> NSString {
let characters: NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var randomString: NSMutableString = NSMutableString(capacity: length)
for i in 0..<length {
var len = UInt32(characters.length)
var rand = arc4random_uniform(len)
randomString.appendFormat("%C", characters.character(at: Int(rand)))
}
return randomString
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return countries[row]
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return countries.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
choosenCountryLabel.text = countries[row]
}
}
and below this is a array full of countries for the picker and is not nessecary to show.
Why not just cast NSMutableArray to a Swift array? For safety I recommend using a guard statement like so:
guard let swiftArrayPosts = posts as? [String] else { return }
Then you can use Swift higher-order functions like filter to find the posts you want:
let filteredPosts = swiftArrayPosts.filter { $0 == "MyQuery" }
Then assign filteredPosts to whatever data source you are using to display them.