Cant get access to an object's values of a cell in TableView [Swift] - swift

I created a class called BlogPost and a tableView. The tableView get filled with many cells and each cell presents the data of a different user (a blogPost object). In each cell there's a button with a phone icon and I want that everytime the user presses on the phone button in each cell, it will call the number of the specific object in that cell. The problem is that in the button function there is no access to the objects value. The line:
var num = blogPost.phone
works inside the setBlogPost function, but not in the button function outside the setBlogPost function :
#IBAction func whatsAppButton(_ sender: Any) {
//doesnt work
var num = blogPost.phone
openWhatsapp(number: num)
}
num gets an error of "Use of unresolved identifier 'blogPost'.
Full code:
import UIKit
class Tavla: UITableViewCell {
#IBOutlet weak var nameLabelTavla: UILabel!
#IBOutlet weak var locationButtonTavla: UIButton!
#IBOutlet weak var phoneButtonTavla: UIButton!
fileprivate let application = UIApplication.shared
func setBLogPost(blogPost: BlogPost) {
nameLabelTavla.text = blogPost.name
if blogPost.elsertext != "" {
locationButtonTavla.backgroundColor = UIColor(red: 135/255, green: 197/255, blue: 113/255, alpha: 0.5)
locationButtonTavla.setTitle("", for: .normal)
}
else{
}
//num works fine
var num = blogPost.phone
}
#IBAction func whatsAppButton(_ sender: Any) {
//num gets an error of "Use of unresolved identifier 'blogPost'
var num = blogPost.phone
openWhatsapp(number: num)
}
func openWhatsapp(number: String){
let urlWhats = "whatsapp://send?phone=\(number)&abid=12354&text=לעדכן מיקום באפליקציה"
if let urlString = urlWhats.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed) {
if let whatsappURL = URL(string: urlString) {
if UIApplication.shared.canOpenURL(whatsappURL) {
UIApplication.shared.open(whatsappURL)
} else {
print("Install Whatsapp")
}
}
}
}
}

Make num a member var:
class Tavla: UITableViewCell {
var num: Int?
// ...
func setBLogPost(blogPost: BlogPost) {
// ...
num = blogPost.phone
}
#IBAction func whatsAppButton(_ sender: Any) {
guard let num = num else { return }
openWhatsapp(number: num)
}

assign the blogPost id to button tag like
button.tag = post.id
in CellForItemAt function, Now you have reference to the blogpost.You can get the clicked blogpost using the button tag like this
#IBAction func whatsAppButton(_ sender: Any) {
let post = blogPostsArray.filter({$0.id == sender.tag})[0]
var num = post.phone
openWhatsapp(number: num)
}

You just set num value in setBlog method as an inner variable. Then num variable can only access the setBlog method. And blogPost object can accessible on setBlog method, not in another method in this class. If you need to access blogPost object in this class then you need to maintain a variable in this class.
Like this:
class Tavla: UITableViewCell {
var blogPost: BlogPost?
...
func setBLogPost(blogPost: BlogPost) {
self.blogPost = blogPost
....
//num works fine
var num = blogPost.phone // because it's access the param value
}
#IBAction func whatsAppButton(_ sender: Any) {
// Now can access the blogPost variable on this class
if let num = self.blogPost?.phone {
openWhatsapp(number: num)
}
}
}
Now the blogPost object can be accessible in whatsAppButton method.

Related

How Do I Fix My Activity Indicator From Not Appearing/Freezing?

#IBOutlet weak var Input: UITextField!
#IBOutlet weak var Heads: UILabel!
#IBOutlet weak var working: UIActivityIndicatorView!
#IBAction func Toss(_ sender: Any) {
DispatchQueue.global().sync {
//set up variables and method
var input = 0
func integer(from textField: UITextField) -> Int {
guard let text = textField.text, let number = Int(text) else {
return 0
}
return number
}
var runningTotal = 0
//collect input
input = integer(from: Input)
//start loading symbol
DispatchQueue.main.async() { [self] in
working.startAnimating()
}
//do math
for _ in 0..<input {
let currentTrial = Int.random(in: 0...1)
if(currentTrial == 1) {
runningTotal += 1
}
}
DispatchQueue.main.async { [self] in
//set output
Heads.text = String(runningTotal)
//stop loading symbol
working.stopAnimating()
}
}
}
This program calculates the number of heads flipped when conducting x coin flips (specified by the user). My activity spinner does not show up at all, even though it is set up to appear when animated. Any help would be appreciated!
So, start by going and having a look at the documentation for DispatchQueue
The sync function reads...
func sync(execute: () -> Void) Submits a block object for execution
and returns after that block finishes executing.
which is not what you want, this will block the main thread and prevent the UI from been updated.
Instead, what you want to use is one of the async variants, for example...
func async(group: DispatchGroup?, qos: DispatchQoS, flags:
DispatchWorkItemFlags, execute: () -> Void) Schedules a block
asynchronously for execution and optionally associates it with a
dispatch group.
You should also get the input value before you run the background thread, as you should avoid accessing components outside of the context of the main thread.
Runnable example...
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var working: UIActivityIndicatorView!
#IBOutlet weak var headsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
headsLabel.isHidden = true
}
#IBAction func doStuff(_ sender: Any) {
var input = 0
func integer(from textField: UITextField) -> Int {
guard let text = textField.text, let number = Int(text) else {
return 0
}
return number
}
input = integer(from: textField)
working.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
//set up variables and method
var runningTotal = 0
//do math
for _ in 0..<input {
let currentTrial = Int.random(in: 0...1)
if(currentTrial == 1) {
runningTotal += 1
}
}
DispatchQueue.main.async { [self] in
headsLabel.isHidden = false
//set output
headsLabel.text = String(runningTotal)
//stop loading symbol
working.stopAnimating()
}
}
}
}

How to set up a NSComboBox using prepare for segue or init?

I am really struggling to pass the contents of one array from a view controller to another to set up the contents of a nscombobox. I have tried everything I can think of, prepare for segue, init; but nothing seems to work.
the program flow is as follows: the user enter a number into a text field and based on it an array with the size of the number is created. Once the user presses a button the next VC appears that has a combo box and inside that combo box those numbers need to appear. All my attempts result in an empty array being passed. Could someone please take a bit of time and help me out. Im sure I'm doing a silly mistake but cannot figure out what.
Code listing below:
Class that take the user input. At this stage I'm trying to pass the contents of the array in the next class as I gave up on prepare for segue because that one crashes because of nil error. Please note that prepare for segue is uncommented in the code listing just for formatting purposes here. Im my program it is commented out as I am using perform segue at the moment.
Any solution would be nice please. Thank you.
import Cocoa
class SetNumberOfFloorsVC: NSViewController {
//MARK: - Properties
#IBOutlet internal weak var declaredNumber: NSTextField!
internal var declaredFloorsArray = [String]()
private var floorValue: Int {
get {
return Int(declaredNumber.stringValue)!
}
}
//MARK: - Actions
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction private func setNumberOfFloors(_ sender: NSButton) {
if declaredNumber.stringValue.isEmpty {
let screenAlert = NSAlert.init()
screenAlert.messageText = "Please specify the number of floors!"
screenAlert.addButton(withTitle: "Got it!")
screenAlert.runModal()
} else if floorValue == 0 || floorValue < 0 {
let screenAlert = NSAlert.init()
screenAlert.messageText = "Please input a correct number of floors!"
screenAlert.addButton(withTitle: "Got it!")
screenAlert.runModal()
} else {
for i in 0...floorValue - 1 {
declaredFloorsArray.append(String(i))
}
print("\(declaredFloorsArray)")
let declareNumberOfRoomsVC = SetNumberOfRoomsForFloorVC(boxData: declaredFloorsArray)
declareNumberOfRoomsVC.boxData = declaredFloorsArray
performSegue(withIdentifier: "set number of rooms", sender: self)
}
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
if segue.identifier == "set number of rooms" {
if let addRoomsVC = segue.destinationController as? SetNumberOfRoomsForFloorVC {
addRoomsVC.floorBox.addItems(withObjectValues: declaredFloorsArray)
}
}
}
}
this is the class for the next VC with the combo box:
import Cocoa
class SetNumberOfRoomsForFloorVC: NSViewController, NSComboBoxDelegate, NSComboBoxDataSource {
//MARK: - Properties
#IBOutlet internal weak var floorBox: NSComboBox!
#IBOutlet private weak var numberOfRoomsTxtField: NSTextField!
internal var boxData = [String]()
//MARK: - Init
convenience init(boxData: [String]) {
self.init()
self.boxData = boxData
}
//MARK: - Actions
override func viewDidLoad() {
super.viewDidLoad()
floorBox.usesDataSource = true
floorBox.dataSource = self
floorBox.delegate = self
print("\(boxData)")
}
#IBAction private func setRoomsForFloor(_ sender: NSButton) {
}
//MARK: - Delegates
func numberOfItems(in comboBox: NSComboBox) -> Int {
return boxData.count
}
func comboBox(_ comboBox: NSComboBox, objectValueForItemAt index: Int) -> Any? {
return boxData[index]
}
}
First you should remove the following code.
let declareNumberOfRoomsVC = SetNumberOfRoomsForFloorVC(boxData: declaredFloorsArray)
declareNumberOfRoomsVC.boxData = declaredFloorsArray
I assume you think that the viewController you created here is passed to prepareForSegue. However the storyboard instantiates a new viewController for you.
After that you need to set your declaredFloorsArray as the the boxData of the new viewController in prepareForSegue and you should be good to go.

Swift: display a random array index every button click

I have a swift class that reads lines from a text document and prints out the first line. After, every time a button is clicked a new line is read out.
What I want is to have a random line printed out the first time, and then a random line printed out after every button click.
Here's what I have so far:
import Foundation
import UIKit
class InfoController: UIViewController {
// MARK: Properties
#IBOutlet weak var difficultylevel: UILabel!
var i:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func readFile(){
if let path = NSBundle.mainBundle().pathForResource("easymath", ofType: "txt"){
var data = String(contentsOfFile:path, encoding: NSUTF8StringEncoding, error: nil)
if let content = data {
let myStrings = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
let randomIndex = Int(arc4random_uniform(UInt32(myStrings.count)))
difficultylevel.text = myStrings[randomIndex]
}
}
}
#IBAction func difficultybutton(sender: UIButton) {
difficultylevel.text = // TODO insert random index of "myStrings" array here
}
}
However, I cannot access the myStrings array at the TODO portion inside the button click. Any help on how to set this up?
Variable scope in Swift is limited to the brackets of the function. So to make myStrings available outside of your readFile() function, you need to declare it as a property for the class:
#IBOutlet var difficultyLevel: UILabel? // BTW your IBOutlet should not be weak
var i: Int = 0
var myStrings: [String]?
Since you are going to use the random functionality over and over, we can abstract the function like this
func randomString() -> String? {
if let strings = myStrings {
let randomIndex = Int(arc4random_uniform(UInt32(myStrings.count)))
return strings[randomIndex]
}
return nil
}
then your instantiation will be like this
if let content = data {
myStrings = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
difficultyLevel.text = randomString()
}
Then, your difficultybutton function will be (with an abstracted random string function)
// Changed the name for better readibility
#IBAction func difficultyButtonTapped(sender: UIButton) {
difficultyLevel.text = randomString()
}
Finally, there isn't any code that calls the readFile function, so you should add it to probably the viewDidLoad function as #CharlesCaldwell points out
override func viewDidLoad() {
super.viewDidLoad()
readFile()
}

Call a func from another class in swift

I would like to call a function which is coded on another class.
So far I have made a struct on the file structs.swift for my data:
struct defValues {
let defCityName: String
let loadImages: Bool
init(defCity: String, loadImgs: Bool){
self.defCityName = defCity
self.loadImages = loadImgs
}
}
I have made the file Defaults.swift containing:
import Foundation
class DefaultsSet {
let cityKey: String = "default_city"
let loadKey: String = "load_imgs"
func read() -> defValues {
let defaults = NSUserDefaults.standardUserDefaults()
if let name = defaults.stringForKey(cityKey){
print(name)
let valuesToReturn = defValues(defCity: name, loadImgs: true)
return valuesToReturn
}
else {
let valuesToReturn = defValues(defCity: "No default city set", loadImgs: true)
return valuesToReturn
}
}
func write(city: String, load: Bool){
let def = NSUserDefaults.standardUserDefaults()
def.setObject(city, forKey: cityKey)
def.setBool(load, forKey: loadKey)
}
}
in which I have the two functions read, write to read and write data with NSUsersDefault respectively.
On my main ViewController I can read data with:
let loadeddata: defValues = DefaultsSet().read()
if loadeddata.defCityName == "No default city set" {
defaultCity = "London"
}
else {
defaultCity = loadeddata.defCityName
defaultLoad = loadeddata.loadImages
}
But when I try to write data it gives me error. I use this code:
#IBOutlet var settingsTable: UITableView!
#IBOutlet var defaultCityName: UITextField!
#IBOutlet var loadImgs: UISwitch!
var switchState: Bool = true
#IBAction func switchChanged(sender: UISwitch) {
if sender.on{
switchState = true
print(switchState)
}else {
switchState = false
print(switchState)
}
}
#IBAction func saveSettings(sender: UIButton) {
DefaultsSet.write(defaultCityName.text, switchState)
}
You need an instance of the DefaultsSet class
In the view controller add this line on the class level
var setOfDefaults = DefaultsSet()
Then read
let loadeddata = setOfDefaults.read()
and write
setOfDefaults.write(defaultCityName.text, switchState)
The variable name setOfDefaults is on purpose to see the difference.
Or make the functions class functions and the variables static variables and call the functions on the class (without parentheses)
From the code you posted, it seems you either need to make the write method a class method (just prefix it with class) or you need to call it on an instance of DefaultsSet: DefaultsSet().write(defaultCityName.text, switchState).
Another issue I found is that you also need to unwrapp the value of the textField. Your write method takes as parameters a String and a Bool, but the value of defaultCityName.text is an optional, so String?. This results in a compiler error.
You can try something like this:
#IBAction func saveSettings(sender: UIButton) {
guard let text = defaultCityName.text else {
// the text is empty - nothing to save
return
}
DefaultsSet.write(text, switchState)
}
This code should now compile and let you call your method.
Let me know if it helped you solve the problem

How to know which NSCombobox selector calling the Delegate

I have the following code written in SWIFT for OS X App, the code is working fine (NSComboBox are select able only, not editable)
I have these two IBOutlet projNewProjType and projNewRouter, when I change the the selection of either of the NSComboBox, I can see the correct selected Index value and String value but how to check that the returned Index value is from projNewProjType NOT projNewRouter in the comboBoxSelectionDidChange()
import Cocoa
class NewProjectSetup: NSViewController, NSComboBoxDelegate {
let comboxProjValue: [String] = [“No”,”Yes”]
let comboxRouterValue: [String] = ["No","Yes"]
#IBOutlet weak var projNewProjType: NSComboBox!
#IBOutlet weak var projNewRouter: NSComboBox!
#IBAction func btnAddNewProject(sender: AnyObject) {
print(“Add New Button Pressed!”)
}
#IBAction func btnCancel(sender: AnyObject) {
self.dismissViewController(self)
}
override func viewDidLoad() {
super.viewDidLoad()
addComboxValue(comboxProjValue,projNewProjType)
addComboxValue(comboxRouterValue,projNewRouter)
self.projNewProjType.selectItemAtIndex(0)
self.projNewRouter.selectItemAtIndex(0)
self.projNewProjType.delegate = self
self.projNewRouter.delegate = self
}
func comboBoxSelectionDidChange(notification: NSNotification) {
let comboBox: NSComboBox = (notification.object as? NSComboBox)!
print("comboBox comboBox: \(comboBox)")
/* This printed “<NSComboBox: 0x6000001e1a00>”*/
print("comboBox objectValueOfSelectedItem: \(comboBox.objectValueOfSelectedItem)")
/* This printed the correct selected String value */
print("comboBox indexOfSelectedItem: \(comboBox.indexOfSelectedItem)")
/* This printed the correct selected Index value */
}
/* Add value to Combo box */
func addComboxValue(myVal:[String],myObj:AnyObject){
let myValno: Int = myVal.count
for i in 0..<myValno{
myObj.addItemWithObjectValue(myVal[i])
}
}
}
You already know the addresses of your two NSComboBox outlets and you know the address of which NSComboBox caused that notification to trigger, so why not do something like:
func comboBoxSelectionDidChange(notification: NSNotification) {
let comboBox: NSComboBox = (notification.object as? NSComboBox)!
if comboBox == self.projNewProjType
{
print("selection changed via self.projNewProjType")
}
if comboBox == self.projNewRouter
{
print("selection changed via self.projNewRouter")
}
You can set identifiers to your NSComboBoxes in IB. Select your combo box and choose identity inspector and name identifier. Then you are able to do like this:
if comboBox.identifier == "someIdentifier" {
// Do something
}