Why my data is lost in singleton? Please take a look:
class YtDataManager {
static var shared_instance = YtDataManager()
let apiKey = /*...*/
let /*...*/
var channelData = [NSObject:AnyObject]()
var videosArray = [[NSObject:AnyObject]]()
var playlistId: String { return self.channelData["playlistId"] as! String}
var urlStringForRequestChannelDetails: String { return String("https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&forUsername=\(self./*ChannelName*/)&key=\(self.apiKey)") }
var urlStringForRequestChannelVideos: String { return String("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(self.playlistId)&key=\(self.apiKey)") }
func fn1() { /*...*/ }
func fn2() { /*...*/ }
// class definition continue...
Look, from one function(fn1) I'm writing data to channelData:
//...
self.channelData["title"] = snippetDict["title"]
self.channelData["playlistId"] = ((firstItemDict["contentDetails"] as! [NSObject:AnyObject])["relatedPlaylists"] as! [NSObject:AnyObject])["uploads"]
//...
and so on... From second function(fn2), I'm reading data:
//...
let targetURL = NSURL(fileURLWithPath: self.urlStringForRequestChannelVideos)
//...
Hence urlStringForRequestChannelVideos is computational property it uses playlistId (look code above).
Here I was surprised about emptiness of channedData from second function(I saw it in Debug mode, also I printed it's count to stdout outside of function). Why????
class YtFeedViewController: UIViewController {
#IBOutlet weak var menuButton:UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
YtDataManager.shared_instance.fn1()
print(YtDataManager.shared_instance.channelData.count) //0
YtDataManager.shared_instance.fn2() //errorness
}
self.navigationItem.title = "YouTube feed"
// Do any additional setup after loading the view.
}
Related
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'm trying to bind a UITextField to a viewModel, however whatever i do i keep getting Cannot invoke 'bind' with an argument list of type '(to: EmailViewModel). What am i doing wrong?
SignUpViewModel
class SignUpViewModel {
let model: SignUpModel
private let disposeBag = DisposeBag()
let emailFieldViewModel = EmailViewModel()
init(model :SignUpModel) {
self.model = model
}
}
EmailViewModel
struct EmailViewModel : FieldViewModel {
var value: Variable<String> = Variable("")
var errorValue: Variable<String?> = Variable(nil)
let title = "Email"
let errorMessage = "Email is wrong"
func validate() -> Bool {
let emailPattern = "[A-Z0-9a-z._%+-]+#([A-Za-z0-9.-]{2,64})+\\.[A-Za-z]{2,64}"
guard validateString(value.value, pattern:emailPattern) else {
errorValue.value = errorMessage
return false
}
errorValue.value = nil
return true
}
}
viewcontroller
class SignUpViewController: UIViewController {
#IBOutlet var emailField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
configureBinding()
}
private func configureBinding() {
// binding
self.emailField.rx.text.bind(to: viewModel.emailFieldViewModel)
}
}
The bind function expects an object that conforms to the ObserverType protocol. Here, EmailViewModel does not conform to that type, hence the error.
Writing an extension to make EmailViewModel conform to the ObserverType protocol would solve the compilation error.
extension EmailViewModel: ObserverType {
func on(_ event: Event<String?>) {
switch event {
case .next(let newValue): value.value = newValue ?? ""
case .error(_), .completed: fatalError("Completion and error are not handled")
}
}
}
//Patient class
import Foundation
struct Patients {
var family: NSArray
var given: NSArray
var id: String
var birthdate:String
var gender: String
}
struct Address {
var city: String
var country: String
var line: NSArray
}
class Patient {
var flag = 0
var address = Address(city: "", country: "", line: [""])
var patient_info = Patients(family: [""], given: [""], id: "", birthdate: "", gender: "")
var response : AnyObject?
init(response: AnyObject) {
self.response = response
if let entry = response.objectForKey("entry") {
//MARK: Address
if let resource = entry[0].objectForKey("resource") {
if let add = resource.objectForKey("address") {
address.city = add[0].objectForKey("city")! as! String
address.country = add[0].objectForKey("country")! as! String
address.line = add[0].objectForKey("line")! as! NSArray
//MARK: patient
patient_info.birthdate = resource.objectForKey("birthDate")! as! String
patient_info.gender = resource.objectForKey("gender")! as! String
if let name = resource.objectForKey("name") {
patient_info.family = name[0].objectForKey("family")! as! NSArray
patient_info.given = name[0].objectForKey("given")! as! NSArray
}
}
}
//MARK: id
if let link = entry[0].objectForKey("link") {
if let url = link[0].objectForKey("url") {
let id = url.componentsSeparatedByString("/")
patient_info.id = id[id.count-1]
}
}
}
print(patient_info)
}
}
//ViewController class
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
var viewModel = ViewModel()
#IBOutlet weak var family_name: UITextField!
#IBOutlet weak var given_name: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
family_name.delegate = self
given_name.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
switch textField {
case family_name:
family_name.resignFirstResponder()
given_name.becomeFirstResponder()
case given_name:
given_name .resignFirstResponder()
default:
print("")
}
return true
}
#IBAction func search(sender: UIButton) {
let family_name1 = family_name.text!
let given_name1 = given_name.text!
viewModel .searchForPatient(family_name1, given_name: given_name1)
//When the name property from my patient class changed I can call the //below method. How to implement the observer?
performSegueWithIdentifier("showSegue", sender:sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?){
if segue.identifier == "showPatientSegue" {
if let displayViewController = segue.destinationViewController as? DisplayViewController {
displayViewController.viewModelDisplay.patient = viewModel.patient
}
}
}
}
// ViewModel where I make the request.
import Foundation
import Alamofire
import SystemConfiguration
class ViewModel {
var patient = Patient!()
func searchForPatient(family_name: String, given_name : String) {
let header = ["Accept" : "application/json"]
Alamofire.request(.GET, "https://open-ic.epic.com/FHIR/api/FHIR/DSTU2/Patient?family=\(family_name)&given=\(given_name)", headers: header).responseJSON { response in
self.patient = Patient(response: response.result.value!)
}
}
func checkInternetConnection() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
}
The problem is that the view loads fester than the request and I need to observe when a property has been changed in my Patient class, so the view can be loaded. If the view loads faster than the request I can't display the Patient information which I need.
You have lots options:
Store a delegate (weak!) object to the ViewController so that when your patient finishes, you can load the view. In the meantime, display something sensible in the view instead.
Send an NSNotification out, which the ViewController is a listener for.
KVO (Explanation of it here, just search for 'key-value observing'), which would require your Patient object to extend NSObject so that you can leverage objc's KVO.
Hope that helps!
You can add an observer on your variable this way :
var yourVariable:String!{
didSet{
refreshView()
}
}
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)
}
}
I have a TextField in my settingsController that i can modify selecting a name from a pickerView, the problem is this : when i change the text of my textField i also change the text of a label in another controller, it work but when i close and reopen my app the label is empty, i can't find a way to save the text that i give it with the pickerView.
My code in the settingsController
override func viewDidLoad() {
super.viewDidLoad()
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
if let firstNameIsNotNill = defaults.objectForKey("firstName") as? String {
self.currencyLabel.text = defaults.objectForKey("firstName") as String
}
currencyLabel.delegate = self
}
func currencyDoneClicked(sender: UIBarButtonItem) {
var myRow = picker.selectedRowInComponent(0)
currencyLabel.text = pickerData.objectAtIndex(myRow) as NSString
DataManager.sharedInstance.contenitore = currencyLabel.text
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.currencyLabel.text, forKey: "firstName")
defaults.synchronize()
}
DataManager :
import UIKit
class DataManager: NSObject {
class var sharedInstance:DataManager {
get {
struct Static {
static var instance : DataManager? = nil
static var token : dispatch_once_t = 0
}
dispatch_once(&Static.token) { Static.instance = DataManager() }
return Static.instance!
}
}
var contenitore : String!
}
And in the other controller :
override func viewDidLoad() {
super.viewDidLoad()
labelCurrency.text = DataManager.sharedInstance.contenitore
}
Just use NSUserDefaults.
NSUserDefaults.standardUserDefaults().setValue(textField.text, forKey: "savedTextField")
Then, when you start up again, populate the field in viewDidLoad or viewWillAppear.
if let text = NSUserDefaults.standardUserDefaults().stringForKey("savedTextField") {
textField.text = text
}