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
Related
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.
I have a usermodel that checks the backend if the email exists - then I drill back into a viewcontroller and set a boolean value that should trigger a function run. However the value is unchanged and I am trying to change this value from the usermodel but it is not accessible. I understand why it does not work.. but do not know how to resolve the issue.
static func sendEmailWithResetLink(email: String) {
let params : Parameters = [
PARAM_EMAIL : email
]
request(URL_RESET_PASSWORD as String, method: .post, parameters: params, headers: nil).responseJSON {
(response: DataResponse<Any>) in
hideProgress()
print("this is response \(response)")
switch(response.result)
{
case .success(_):
print("it did not fail")
let passwordResetVC = PasswordResetViewController()
passwordResetVC.hasFailed = false
break
case .failure(_):
print("it failed")
let passwordResetVC = PasswordResetViewController()
//here boolean is set that I am trying to access in viewcontroller
passwordResetVC.hasFailed = true
break
}
}
}
Here's what I would suggest. You probably have some of these in place already:
Create an PasswordResetViewController object has an #IBAction func resetButtonClicked triggered by a button or whatever, which kicks off the password reset process.
Create a UserManager class. This class is responsible for all profile management activies in your app. Among other things, it has the ability to reset user passwords. This UserManager would probably be a singleton, that' sprobably good enough for now.
Create a new UserManagerDelegate protocol. Add to it all capabilities that are required by the UserManager to inform them of whatever happened. For example: var passwordResetHasFailed: Bool { get set }.
Extend your PasswordResetViewController conform to this protocol.
Your VC gets a reference to the singleton UserManager object, stores it in an instance variable, and uses that to access the shared object from then on.
Make your PasswordResetViewController register itself as the delegate to the user manager, with userManager.delegate = self
The #IBAction func resetButtonClicked will just call userManager.resetPassword()
Your UserManager does whatever it needs to do to reset the user's password.
When it's done, it'll call self.delegate?.passwordResetHasFailed = true/false.
Since your PasswordResetViewController registered itself as the delegate of the UserManager, when the operation is done, its passwordResetHasFailed property will be changed, giving it a chance to respond (by updating some UI or whatever).
There are some limitations to this approach, but it's a decent way to get started. Some thing to note:
This lets you unit test your PasswordResetViewController. You can create a MockUserManager, and set tesPasswordResetViewController.userManager = MockUserManager(), allowing you to separate out the user manager, and test PasswordResetViewController in isolation.
You'll run into issues if you need multiple objects to subscribe to receive delegate call backs (since there can only be 1 delegate object). At that point, you can switch to using something like Promises, RxSwift or Combine. But that's a problem for a later time, and the migration would be easy.
Going off of #Alexander - Reinstate Monica and what I assume what the code to look like to approach your problem.
Using MVC:
In Models folder (data/ logic part)
public class User {
private var name: String!
private var userEmail: String!
public var hasFailed: Bool?
init() {
name = ""
userEmail = ""
hasFailed = nil
}
public func setName(name: String) { self.name = name }
public func getName() -> String { return name }
public func setEmail(email: String) { userEmail = email }
public func getEmail() ->String { return userEmail }
public static func sendEmailWithRestLing(email: String) {
// your other code
switch response.result {
case .success(_):
//your code
hasFailed = false
break
case .failuare(_):
// your code
hasFailed = true
break
}
}
}
User Manager class applying singleton design
final class UserManager {
private var user = User()
static let instance = UserManager()
private init(){}
public func userName(name: String) {
if (name.count > 3) {
user.setName(name: name)
}
else { print("user name is too short") }
}
public func userEmail(email: String) {
if (email.count > 3) {
user.setEmail(email: email)
}
else { print("user email is too short") }
}
public func getUserName() -> String {
let name = user.getName()
if (name.isEmpty) { return "user name is Empty" }
return name
}
public func getUserEmail() -> String {
let email = user.getEmail()
if (email.isEmpty) { return "user email is Empty" }
return email
}
public func doKatieTask(link: String) -> Int {
guard let myValue = user.hasFailed else {
return -1
}
if (myValue) { return 1}
return 0
}
}
So, Now in the Controllers folder and since we a one-to-one relation we will use delegate design pattern. If had had one-to-many with the view controller. Use observers.
class ViewController: UIViewController {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
var _hasFail: Bool!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func doTask() {
UserManager.instance.userName(name: nameTextField.text!)
UserManager.instance.userEmail(email: emailTextField.text!)
switch UserManager.instance.doKatieTask(link: emailTextField.text!) {
case 0:
_hasFail = false
break
case 1:
_hasFail = true
break
default:
print("hasFailed is nil")
break
}
if let vc = storyboard?.instantiateViewController(identifier: "passwordVC") as? PasswordResetViewController {
vc.modalPresentationStyle = .fullScreen
vc.delegate = self
self.present(vc, animated: true, completion: nil)
}
}
}
extension ViewController: KatieDelegate {
var hasFailed: Bool {
get {
return _hasFail
}
set {
_hasFail = newValue
}
}
}
In PasswordReset UIViewController
protocol KatieDelegate {
var hasFailed: Bool { get set }
}
class PasswordResetViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
var delegate: KatieDelegate?
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = UserManger.instance.getUserName()
emailLabel.text = UserManger.instance.getUserEmail()
if let delegate = delegate {
print("The value for has failed is: .....\(delegate.hasFailed)!")
}
else { print("error with delegate") }
}
}
I´ve a webrequest with jsonserialization, after that, a for-in fetch process.
In whole this takes approximately 5-7 seconds.
After that i want to refersh my tableview in Viewcontroller.
The scheme of the function looks like this.
public struct Container {
let name: String
let symbol: String
let rank: String
}
public var dataArray = [Container]()
func fetchNewData() {
var view = ViewController()
// WebbRquest...
// Json serialization...
// the following list is much longer, will take a while...
for items in json {
let name = items["name"] as? AnyObject;
let symbol = items["symbol"] as? AnyObject;
let rank = items["rank"] as? AnyObject;
let result = Container(name: name! as! String, symbol: symbol! as! String,rank: rank! as! String)
dataArray.append(result)
}
// Now, after alle the work is done, i want to reload the tableview in Viewcontrller:
view.reload()
// Here i´m getting error, because nothing will be executed after return.
}
How can I call the reload function, after the webrequest process is finished? Because after the return, the function doesn´t execute anything anymore.
And no other function will "know" when the fetchNewData() function is finished.
Thanks for any help!
#IBAction func updateButton(_ sender: Any) {
fetchNewData()
}
According Phillipps suggestion, I had to modify the #IBAction func a little bit.
But now it´s working. Awesome!
Here the full working version:
public struct Container {
let name: String
let symbol: String
let rank: String
}
public var dataArray = [Container]()
func fetchNewData(completion:#escaping ([Container])->()) {
var view = ViewController()
// WebbRquest...
// Json serialization...
// the following list is much longer, will take a while...
for items in json {
let name = items["name"] as? AnyObject;
let symbol = items["symbol"] as? AnyObject;
let rank = items["rank"] as? AnyObject;
let result = Container(name: name! as! String, symbol: symbol! as! String,rank: rank! as! String)
dataArray.append(result)
}
completion(dataArray)
}
This is the actionFunc:
#IBAction func upDateButton(_ sender: Any) {
let data = dataArray
fetchNewData() {_ in (data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Here's a start. It will be vague because I'm making guesses about code I can't see, but you may be able to convert it to your own needs.
Change the fetch function so that it takes a closure as a parameter:
func fetchNewData(completion:([Container])->()) {
...note that the closure will accept the data array when it's called.
After you have your json all parsed, you then invoke the closure:
dataArray.append(result)
}
completion(dataArray)
The "magic" is in the view controller where you tell fetchNewData what to do when it's finished. Something like:
#IBAction func updateButton(_ sender: Any) {
fetchNewData() {(data)
// Save the data where the view controller can use it
self.tableArray = data
// Main queue for UI update
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Note that the closure is written in the view controller, so self is the view controller. This means no need to create a second (useless) controller inside the fetch.
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()
}
I would like to call functions residing in other files. In my given example I received no syntax errors, and the app runs. When I click the IBAction, the App crashes. I have no idea why. I am using Xcode 6.4 and coding for OS X
please advise.
// FileUtilties.swift
import Foundation
import Cocoa
class FileUtilities: NSObject, NSAlertDelegate {
var fileList : String!
func listFilesFromDocumentsFolder() -> [String]
{
var theError = NSErrorPointer()
let dirs = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if dirs != nil {
let dir = dirs![0]
let fileList = NSFileManager.defaultManager().contentsOfDirectoryAtPath(dir, error: theError)
return fileList as! [String] // edit: added ! for Swift 1.2 compatibitily
}
else{
let fileList : String!
}
return [fileList]
}
// Alert popup Window
func IdialogOKCancel(myQuestion: String, myText: String) -> Bool {
let myPopup: NSAlert = NSAlert()
myPopup.messageText = myQuestion
myPopup.informativeText = myText
myPopup.alertStyle = NSAlertStyle.WarningAlertStyle
myPopup.addButtonWithTitle("OK")
myPopup.addButtonWithTitle("Cancel")
let res = myPopup.runModal()
if res == NSAlertFirstButtonReturn {
return true
}
return false
}
}
// ViewController.swift
import Cocoa
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
// setsup outlet to add data to ListViewer
#IBOutlet weak var ListViewer: NSScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func AddButtonClicked(sender: AnyObject) {
var rtnText: String = "this is the text "
var rtn: Bool = false
let instance = FileUtilities()
rtn = instance.IdialogOKCancel("Ok?", myText: rtnText)
}
}