I am wondering how to make just 1 view terminate in iOS. I am making an app such that in the viewDidLoad() it generates 52 random strings and appends them to an array. I have it so that when you shake the device, it will perform a segue.
The problem with this is that it keeps the items in the array that I mentioned earlier. This means that when it does the user goes into that view again, it will still have the 52 random strings, plus when it goes through the viewDidLoad function, it will append 52 more strings, so if the user doesn't terminate the whole entire app, but just segues out of the view and goes back in, there will be 104 strings and not 52.
I don't want the above to happen in my app, so I am wondering how you could just terminate this one view while also performing a segue so that this problem won't happen.
Here is some code from my app:
These are the arrays I had:
var theCardsTopPlayerHas = [""]
var theCardsBottomPlayerHas = [""]
var theCardsThatWork = ["2_of_clubs", "2_of_diamonds", "2_of_hearts", "2_of_spades", "3_of_clubs", "3_of_diamonds", "3_of_hearts", "3_of_spades", "4_of_clubs", "4_of_diamonds", "4_of_hearts", "4_of_spades", "5_of_clubs", "5_of_diamonds", "5_of_hearts", "5_of_spades", "6_of_clubs", "6_of_diamonds", "6_of_hearts", "6_of_spades", "7_of_clubs", "7_of_diamonds", "7_of_hearts", "7_of_spades", "8_of_clubs", "8_of_diamonds", "8_of_hearts", "8_of_spades", "9_of_clubs", "9_of_diamonds", "9_of_hearts", "9_of_spades", "10_of_clubs", "10_of_diamonds", "10_of_hearts", "10_of_spades", "jack_of_clubs2", "jack_of_diamonds2", "jack_of_hearts2", "jack_of_spades2", "queen_of_clubs2", "queen_of_diamonds2", "queen_of_hearts2", "queen_of_spades2", "king_of_clubs2", "king_of_diamonds2", "king_of_hearts2", "king_of_spades2","ace_of_clubs", "ace_of_diamonds", "ace_of_hearts", "ace_of_spades"]
Here is what the viewDidLoad method has:
struct shuffledVar {
static var shuffled = [String]();
}
override func viewDidLoad() {
super.viewDidLoad()
upperLabelNumber.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
//MARK: - Shuffling Cards
for _ in 1...52 {
let shuffledCardList = Int(arc4random_uniform(UInt32(theCardsThatWork.count)))
let the_card = theCardsThatWork[shuffledCardList]
print("\(the_card)")
shuffledVar.shuffled.append(the_card)
theCardsThatWork.remove(at: shuffledCardList)
print("Finished Card")
}
theCardsTopPlayerHas = Array(shuffledVar.shuffled[0...25])
theCardsBottomPlayerHas = Array(shuffledVar.shuffled[26...51])
print("")
print(shuffledVar.shuffled)
print("")
print(theCardsTopPlayerHas)
print("")
print(theCardsBottomPlayerHas)
}
This is the code that segues the view:
override func motionBegan(_ motion: UIEventSubtype, with event: UIEvent?) {
performSegue(withIdentifier: "friendToDashboard", sender: self)
}
Thank you!
The problem is that you are using a static var to store your array of strings.
To solve this problem there are several solutions depending on what you need. Here there are a couple of solutions:
Solution 1
If you really need to keep the shuffled array as a Singleto, then you can empty the shuffled array in the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
shuffledVar.shuffled = []
upperLabelNumber.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
...
}
Solution 2
If you do not need to keep the shuffled array as a Singleton, then you can modify it as follows:
struct shuffledVar {
var shuffled = [String]();
}
var shuffled = shuffledVar()
override func viewDidLoad() {
super.viewDidLoad()
upperLabelNumber.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
//MARK: - Shuffling Cards
for _ in 1...52 {
let shuffledCardList = Int(arc4random_uniform(UInt32(theCardsThatWork.count)))
let the_card = theCardsThatWork[shuffledCardList]
print("\(the_card)")
shuffled.shuffled.append(the_card)
theCardsThatWork.remove(at: shuffledCardList)
print("Finished Card")
}
theCardsTopPlayerHas = Array(shuffled.shuffled[0...25])
theCardsBottomPlayerHas = Array(shuffled.shuffled[26...51])
print("")
print(shuffled.shuffled)
print("")
print(theCardsTopPlayerHas)
print("")
print(theCardsBottomPlayerHas)
}
You are using static var shuffled so you are using static variable. Do you really need a static variable? If not instead of:
struct shuffledVar {
static var shuffled = [String]();
}
use:
var shuffled:[String] = []
and then in code use:
shuffled.append(the_card)
But if you need this variable to be static (for example you are using it in some other view controller) just remove all elements before adding a new one:
//MARK: - Shuffling Cards
shuffledVar.shuffled.removeAll()
You can read more about static vars https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html and more about arrays: https://developer.apple.com/documentation/swift/array
Related
I am developing a small app to connect to my site, download data via a PHP web service, and display it in a table view. To get started I was following a tutorial over on Medium by Jose Ortiz Costa (Article on Medium).
I tweaked his project and got it running to verify the Web service was working and able to get the data. Once I got that working, I started a new project and tried to pull in some of the code that I needed to do the networking and tried to get it to display in a tableview in the same scene instead of a popup scene like Jose's project.
This is where I am running into some issues, as I'm still rather new to the swift programming language (started a Udemy course and have been picking things up from that) getting it to display in the table view. I can see that the request is still being sent/received, but I cannot get it to appear in the table view (either using my custom XIB or a programmatically created cell). I thought I understood how the code was broken down, and even tried to convert it from a UITableViewController to a UITableviewDataSource via an extension of the Viewcontroller.
At this point, I'm pretty stumped and will continue to inspect the code and tweak what I think might be the root cause. Any pointers on how to fix would be really appreciated!
Main Storyboard Screenshot
Struct for decoding my data / Lead class:
import Foundation
struct Lead: Decodable {
var id: Int
var name: String
var program: String
var stage: String
var lastAction: String
}
class LeadModel {
weak var delegate: Downloadable?
let networkModel = Network()
func downloadLeads(parameters: [String: Any], url: String) {
let request = networkModel.request(parameters: parameters, url: url)
networkModel.response(request: request) { (data) in
let model = try! JSONDecoder().decode([Lead]?.self, from: data) as [Lead]?
self.delegate?.didReceiveData(data: model! as [Lead])
}
}
}
ViewController:
import UIKit
class LeadViewController: UIViewController {
// Buttons
#IBOutlet weak var newButton: UIButton!
#IBOutlet weak var firstContactButton: UIButton!
#IBOutlet weak var secondContactButton: UIButton!
#IBOutlet weak var leadTable: UITableView!
let model = LeadModel()
var models: [Lead]?
override func viewDidLoad() {
super.viewDidLoad()
//Make Buttons rounded
newButton.layer.cornerRadius = 10.0
firstContactButton.layer.cornerRadius = 10.0
secondContactButton.layer.cornerRadius = 10.0
//Delegate
model.delegate = self
}
//Send request to web service based off Buttons Name
#IBAction func findLeads(_ sender: UIButton) {
let new = sender.titleLabel?.text
let param = ["stage": new!]
print ("findLead hit")
model.downloadLeads(parameters: param, url: URLServices.leads)
}
}
extension LeadViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print ("number of sections hit")
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let _ = self.models else {
return 0
}
print ("tableView 1 hit")
return self.models!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object from LeadCell
let cell = tableView.dequeueReusableCell(withIdentifier: "leadID", for: indexPath) as! LeadCell
// Lead selection
cell.leadName.text = self.models![indexPath.row].name
cell.actionName.text = self.models![indexPath.row].lastAction
cell.stageName.text = self.models![indexPath.row].stage
cell.progName.text = self.models![indexPath.row].program
print ("tableView 2 hit")
// Return the configured cell
return cell
}
}
extension LeadViewController: Downloadable {
func didReceiveData(data: Any) {
//Assign the data and refresh the table's data
DispatchQueue.main.async {
self.models = data as? [Lead]
self.leadTable.reloadData()
print ("LeadViewController Downloadable Hit")
}
}
}
EDIT
So with a little searching around (okay...A LOT of searching around), I finally found a piece that said I had to set the class as the datasource.
leadTable.dataSource = self
So that ended up working (well after I added a prototype cell with the identifier used in my code). I have a custom XIB that isn't working right now and that's my next tackle point.
You load the data, but don't use it. First, add the following statement to the end of the viewDidLoad method
model.delegate = self
Then add the following LeadViewController extension
extension LeadViewController: Downloadable {
func dicReceiveData(data: [Lead]) {
DispatchQueue.main.async {
self.models = data
self.tableView.reloadData()
}
}
}
And a couple of suggestions:
It is not a good practice to use the button title as a network request parameter:
let new = sender.titleLabel?.text
let param = ["stage": new!]
It is better to separate UI and logic. You can use the tag attribute for buttons (you can configure it in the storyboard or programmatically) to check what button is tapped.
You also have several unnecessary type casts in the LeadModel class. You can change
let model = try! JSONDecoder().decode([Lead]?.self, from: data) as [Lead]?
self.delegate?.didReceiveData(data: model! as [Lead])
to
do {
let model = try JSONDecoder().decode([Lead].self, from: data)
self.delegate?.didReceiveData(data: model)
}
catch {}
I have this that download the JSON and I have appended them into an array
func downloadDetails(completed: #escaping DownloadComplete){
Alamofire.request(API_URL).responseJSON { (response) in
let result = response.result
let json = JSON(result.value!).array
let jsonCount = json?.count
let count = jsonCount!
for i in 0..<(count){
let image = json![i]["urls"]["raw"].stringValue
self.imagesArray.append(image)
}
self.tableView.reloadData()
print(self.imagesArray)
completed()
}
}
I know that viewDidLoad loads first and viewDidAppear load later. I am trying to retrieve the array that contain all the informations but it's not showing on viewDidLoad as the array is empty but its showing on viewDidAppear with an array of ten and I dont know how to get that from viewDidAppear.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var imageList: Image!
var imagesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad " ,imagesArray)
setupDelegate()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
downloadDetails {
DispatchQueue.main.async {
}
print("viewDidAppear ", self.imagesArray)
}
}
The output is as follow
viewDidLoad []
viewDidAppear ["https://images.unsplash.com/photo-1464550883968-cec281c19761", "https://images.unsplash.com/photo-1464550838636-1a3496df938b", "https://images.unsplash.com/photo-1464550580740-b3f73fd373cb", "https://images.unsplash.com/photo-1464547323744-4edd0cd0c746", "https://images.unsplash.com/photo-1464545022782-925ec69295ef", "https://images.unsplash.com/photo-1464537356976-89e35dfa63ee", "https://images.unsplash.com/photo-1464536564416-b73260a9532b", "https://images.unsplash.com/photo-1464536194743-0c49f0766ef6", "https://images.unsplash.com/photo-1464519586905-a8c004d307cc", "https://images.unsplash.com/photo-1464519046765-f6d70b82a0df"]
What is the right way to access the array with information in it?
that's because the request is asynchronous it's not getting executed as serial lines of code , there should be a delay according to network status , besides you don't even call downloadDetails inside viewDidLoad
//
you also need to reload table here
downloadDetails { // Alamofire callback by default runs in main queue
self.tableView.reloadData()
}
Just call your method on
viewDidLoad()
And maybe you should learn more about iOS lifecycle, so you can spot the difference between viewDidLoad and viewDidAppear 😉
I'm trying to make some view controllers that save a variable of a label every time and sum it to a total var. At the last view controller, show the result.
My code is:
FirstViewController
#IBAction func playSystemSoundV1(sender: UIButton) {
// Other code...
puntsLocal = 3
}
#IBAction func playSystemSoundRet(sender: UIButton) {
// Other code...
puntsLocal = 5
}
This code is similar for 5 controller. At the end of the controller I have a button to pass from one viewController to another. When I click I do the code above:
#IBAction func puntuacioAction(sender: UIButton) {
let puntuacion = Sum()
puntuacion.sumPoints(puntsLocal)
}
Sum Class
import Foundation
class Sum {
var points = 0
// Method to sum points from every question.
func sumPoints(num: Int) {
self.points += num
}
func getPoints() -> Int {
return points
}
}
The problem is that returns only the last number without do any kind of sum. What can I do? In other languages it's very easy to resolve but in Swift I cannot reach the answer. Help please!
Thanks.
You need to declare your variable outside the function:
let puntuacion = Sum()
#IBAction func puntuacioAction(sender: UIButton) {
puntuacion.sumPoints(puntsLocal)
}
Your original code created a new Sum everytime the function was called, explaining the behavior you are seeing. In Swift, variable declarations are bound to the scope they appear (e.g., inside a function, inside a class, etc).
If, besides that, you want to share this Sum object across many view controllers, this might help:
class Sum {
static let shared = Sum()
...
}
and use it like this:
#IBAction func puntuacioAction(sender: UIButton) {
Sum.shared.sumPoints(puntsLocal)
}
Of course, you should then delete the let puntuacion = Sum() line.
This question already has answers here:
Returning method object from inside block
(3 answers)
Closed 5 years ago.
I have an problem that I can not solve. A lot of questions are in JS and I don't really understand them.
I'm using Firebase as my database for my IOS app and Swift. Here is my situation:
I have a class file that contains functions that can retrieve values in my database. I'm calling these functions in some viewControllers.
The values retrieved by these functions are immediately used in these viewControllers.
My problem is that my app crash because of nil values returned by the class file functions. This happen because of the asynchronous Firebase snapshot.
The variables assumed to contain the values are used before their value is assigned => My app crash, or printed values are nil.
Then my question is simple: How can I structure my code to avoid this issue? I already tried completions, but that's not working for me: functions are still asynchronous.
Here is one of my function in the class file:
func initAverageMark(completionHandler: #escaping (_ mark: Double) -> ()) {
let userRef = ref.child("users").child((user?.uid)!).child("mark")
userRef.observeSingleEvent(of: .value, with: { (snapshot) -> Void in
if let mark: Double = snapshot.value as? Double {
completionHandler(mark)
}
}) { (error) in
print(error.localizedDescription)
}
}
One of my viewController code:
private var totalAsks: Double!
override func viewDidLoad() {
super.viewDidLoad()
initInfos()
}
func initInfos() {
mainUser().initTotalAsks{ total in
self.totalAsks = total
}
initLabels()
}
func initLabels() {
totalAsksLabel.text = " \(totalAsks!)" // it crashs here
}
Assuming you'd want to set some label or something in your viewController to the value of mark you'd do it like this.
mainUser().initTotalAsks { mark in
self.totalAsksLabel.text = " \(mark)"
}
Edit
Or if you absolutely want to use that Double.
private var totalAsks: Double? = nil {
didSet {
initLabels()
}
}
override func viewDidLoad() {
super.viewDidLoad()
initInfos()
}
func initInfos() {
mainUser().initTotalAsks{ total in
self.totalAsks = total
}
}
func initLabels() {
guard totalAsks != nil else {
return
}
totalAsksLabel.text = " \(totalAsks!)"
}
I am attempting have one of my views present the user with the option to reset an array that they have edited to it's original form. I am currently attempting to do this using NSNotificationCenter.
The current process:
User flips through a series of cards, each of which are pulled from the array experiments (which has been shuffled beforehand but I don't think that's relevant to this issue) and deletes ones they don't want to see again when the array loops back around.
User decides they want to start again with a fresh array.
User clicks "Home" which brings them to AdditionalMenuViewController.swift which is set up with NSNotification center with an #IBAction that triggers the notification that the ModelController will listen to. Here's AdditionalMenuViewController.swift.
let mySpecialNotificationKey = "com.example.specialNotificationKey"
class AdditionalMenuViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "resetArray", name: mySpecialNotificationKey, object: nil)
}
#IBAction func notify(){
NSNotificationCenter.defaultCenter().postNotificationName(mySpecialNotificationKey, object: self)
}
func resetArray() {
println("done")
}
}
That should trigger the listener in ModelController to reset the array and then allows the user to continue using the app but with the array reset. Here are the (I think!) relevant parts of ModelController.swift:
class ModelController: NSObject, UIPageViewControllerDataSource {
var experiments = NSMutableArray()
var menuIndex = 0
func shuffleArray(inout array: NSMutableArray) -> NSMutableArray
{
for var index = array.count - 1; index > 0; index--
{
// Random int from 0 to index-1
var j = Int(arc4random_uniform(UInt32(index-1)))
// Swap two array elements
// Notice '&' required as swap uses 'inout' parameters
swap(&array[index], &array[j])
}
return array
}
override init() {
super.init()
// Create the data model.
if let path = NSBundle.mainBundle().pathForResource("experiments", ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) {
experiments.addObjectsFromArray(dict.objectForKey("experiments") as NSArray)
}
}
println(experiments)
shuffleArray(&experiments)
println(experiments)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "resetArray", name: mySpecialNotificationKey, object: nil)
}
func resetArray() {
if let path = NSBundle.mainBundle().pathForResource("experiments", ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) {
experiments.addObjectsFromArray(dict.objectForKey("experiments") as NSArray)
}
}
shuffleArray(&experiments)
}
Currently it does create a new array but it is double the size of the original which tells me its running both the original and the reset functions. It also breaks all the pageViewController functionality that you can find in the default Page-Based Application in Xcode.
What am I missing here?
How can I both reset the array and then drop the user back into the flow of the Page-Based App?
rdelmar's suggestion worked perfectly. I now clear the array and then fill it with a freshly shuffled one.