Swift: Access to another ViewController's field - ios

I would like to have a tabbed application, where one page is reserved for debug info. So on that page, I have a text view. I would like other view controllers and singleton (data access, etc) to be able to add to the text area. I have tried the singleton pattern here, but I think that doesn't work because the singleton instance isn't the same as the "real" one that the application is using. Is there a way to get access to the actual instance from another ViewController, or from other classes in the app?
Here is the DebugViewController:
class DebugViewController : UIViewController {
#IBOutlet weak var debugTextField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
debugTextField.text = ""
}
func debug(text : String) {
if debugTextField == nil {
debugTextField = UITextView()
debugTextField.text = ""
}
println("\(NSDate()) : \(text)")
debugTextField.text = "\(NSDate()) : \(text) \n" + debugTextField.text
}
}

A singleton will work but you can not bind it with IB. You need to create it programmatically in your AppDelegate.
Another idea is simply to place a reference to your DebugViewController inside those classes where you need it like
var sharedDebugViewController: DebugViewController!
and initialize them from you AppDelegate.

You can not assign a value to the other controllers UI like label , textfeild or any other by passing through a variables you can do it
you can do it with a global class so create as
class GlobalVariables : NSObject {
var variableOne : String = String()
}
var global = GlobalVariables()
now when you receive a message do it as global.variableOne = your message
one you have set the variable to the global class you can access this variable in your application any where you want .
on the other controller viewdidload
use as
debugTextView.text = global.variableOne

Related

Accessing ViewController's variables through another ViewController

On my iOS app written in Swift, I have a variable which is initialized on FirstViewController.swift.
I want to assign its value to a label on SecondViewController.swift.
At first I've tried to do it like this on SecondViewController.swift:
var firstViewController: FirstViewController = FirstViewController(nibName: nil, bundle: nil)
var name = firstViewController.name
After the didn't work, I tried to do it using a struct:
// FirstViewController.swift
struct GlobalVariables {
var name: String = "test"
}
// SecondViewController.swift
var name = FirstViewController.GlobalVariables.name
But that didn't work either. After both methods I'm printing the new variable to the console and assign its value to the label, but all I see is nothing on the label and nil on the console.
Can you please help me with that? How can I access to a variable on FirstViewController.swift through SecondViewController.swift?
To pass arguments between View Controllers, you can use segues.
First you have the variable in FirstViewController
FirstViewController: UIViewController {
var name: String = "test"
...
}
Then you have a variable of the same type in SecondViewController
SecondViewController: UIViewController {
var name: String = ""
...
}
To move from FirstViewController, you use a programmatic segue.
self.performSegue(withIdentifier: "Indentifier", sender: nil)
Then, in FirstViewController, define prepareForSegue:sender.
You get a reference to the destination view controller, then set the variable to the one from sending view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! SecondViewController
vc.name = name
}
EDIT:
If you need it to be accessible in all view controllers, define a Global class. You stated in your question you tried a struct. Instead, try static variables
class Global {
static var name: String?
}
EDIT 2:
Another way to have global variables is with a singleton class
class Global {
static let sharedInstance = Global()
var name: String?
}
Here's a barebones idea of how to do it with a separate file to hold data (values) for your labels to display. Disclaimer: it might not be the best idea (you probably don't want to bind your ViewControllers to this single data file like this), but it should do the trick before you learn other better ways. So, here we go.
You want to use a separate file (name it for example MyTextValuesModel.swift) for all the values you'd like your FirstViewController and SecondViewController to access.
You select File > New > File... from the menu, and choose Swift File.
Then you declare your class:
import Foundation
class ValuesForLabels {
var textForLabel1 = "Label1"
var textForLabel2 = "Label2"
var textForLabel3 = "Label3"
// etc...
}
Now in your FirstViewController and SecondViewController you declare a variable to refer to this class:
var textsForLabels = ValuesForLabels()
Now you can access (read/write) the labels from any view controllers like this:
textsForLabels.textForLabel1 = "NewLabel1Text"
// you've just set a new value for Label1
label1.text = textsForLabels.textForLabel1
// your label1 text is now set to textForLabel1
If you'd want to access label values from another view controller, add a new variable to your new view controller to reference the labels.

Create a member for the UIViewController class

I tried to do some research but couldn't figure it out, so is it possible to create a member for the class UIViewController, or any class for that matter?
In every single one of my UIViewController subclasses I declare the data member
userdata = [NSManagedObject]()
So I was wondering if I could declare the variable "userdata" inside the actual UIViewController class, either directly or through an external file.
You can simply create a sub-class of UIViewController which has the userdata property and then derive all of your view controllers from that class instead of UIViewController
Something like:
class BaseViewController:UIViewContrller {
var userdata = [NSManagedObject]()
}
class NewViewController:BaseViewController {
// Your sub view controller implementation goes here
}
You should use extensions.
extension UIViewController {
var userData : [NSManagedObject] {
get {
return [NSManagedObject]()
}
}
}
If you don't want every UIViewController to have that property, you will have to use subclassing.
class DataViewController:UIViewContrller {
var userdata = [NSManagedObject]()
}
class NewViewController:DataViewController {
// Do something stuff to the View here
}
You can use extensions if userData is a computed property:
extension UIViewController {
var userData: [NSManagedObject] {
get { return an array from somewhere else }
set { set the value to somewhere else }
}
}
If your property is not computed but stored, you must use a subclass:
class BaseViewController: UIViewController {
var userData: [NSManagedObject] = []
}
And make every VC of yours inherit this class. The disadvantage of using this approach is that your view controllers can't inherit any other class, like UITableViewController.
So here is the best method I came up with.
Create a protocol:
protocol MyVC {
var userData: [NSManagedObject] { get set }
}
Now make every VC of yours conform to this protocol. In every VC, just start typing userData and use enter to select the right completion that Xcode provides and the property will be automatically added for you. If you forgot to do this, the compilation will fail.

Updating a UILabel via protocol results in crash (found nil)

I want to implement the MVP pattern for a new app. So the View shouldn't have any logic besides one that exclusively concerns UI elements. Therefore I want to request initial data from an "Interpreter" (interpreting user input in later code), which in turn requests data from my model and gives it to the "Presenter". The presenter holds a protocol with functions of the view.
The problem is: Calling updateUIData() from the presenter results in a
fatal error: unexpectedly found nil while unwrapping an Optional value
while calling the function from within the View at the same position is working just fine.
I suspect the error comes from the initialization of the specific MainViewController in the init of the presenter, but I don't know how to resolve this, if my guess is right.
Here's my (relevant) code:
MainViewController:
class MainViewController: UIViewController {
lazy var interpreter = Interpreter() // lazy needed b/c Interpreter holds Presenter which holds MainViewController
#IBOutlet var dateLabel: UILabel!
#IBOutlet var totalTimeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// updateUIData()
requestData()
}
func requestData() {
interpreter.requestData()
}
}
extension MainViewController: MainViewSetters {
func updateUIData() {
dateLabel.text = "Data"
totalTimeLabel.text = "loaded"
}
}
MainViewSetters (Protocol):
protocol MainViewSetters {
func updateUIData()
}
Interpreter:
class Interpreter {
let presenter = Presenter()
func requestData() {
// normally: get data from model and pass it to presenter
presenter.presentData()
}
}
Presenter:
class Presenter {
let mainView: MainViewSetters
init(withMainViewController mainVC: MainViewSetters = MainViewController()) {
mainView = mainVC
}
func presentData() {
mainView.updateUIData()
}
}
Your problem here is that you are not passing the reference to MainViewController to your instance of Presenter.
This code :
lazy var interpreter = Interpreter()
Should be more like this : (Type is needed here because with lazy the compiler can't infer properly)
lazy var interpreter: Interpreter = Interpreter(for: self)
You then have to create a special initializer in Interpreter which will pass the viewController instance to its presenter property :
class Interpreter {
let presenter: Presenter
init(for viewController: MainViewSetters) {
presenter = Presenter(withMainViewController: viewController)
}
func requestData() {
// normally: get data from model and pass it to presenter
presenter.presentData()
}
}
I also highly suggest you to remove the default value to Presenter's init method, it's very unlikely you'll want to assign a random instance of MainViewController as mainView of any Presenter object.
Finally, please note that this code is creating a retain cycle and neither your MainViewController instance nor your Presenter instance will be deallocated. This is due to the fact the Presenter class holds a strong reference to the MainViewController instance with its property mainView. To fix this you have to mark the mainView as weak as well as making it optional.
Please see the fixed implementation below :
class Presenter {
weak var mainView: MainViewSetters?
init(withMainViewController mainVC: MainViewSetters) {
mainView = mainVC
}
func presentData() {
mainView?.updateUIData()
}
}
For weak to be acceptable on a property of type MainViewSetters (which is not a real type but only a protocol) you have to specify that its a protocol that will only be applied to classes :
protocol MainViewSetters: class {
func updateUIData()
}
You are initializing interpreter passing a default MainViewController().
Change that code from:
lazy var interpreter = Interpreter()
to
lazy var interpreter = Interpreter(withMainViewController: self)

#obj protocol delegate method not working in second class. Swift

Im trying to call protocol delegate in an additional class. The first class (ViewController) works but the second one I have implemented it in doesn't show any data. I added it with the autofill option so its not giving errors. It just doesn't do anything.
sender class
#objc protocol BWWalkthroughViewControllerDelegate{
#objc optional func walkthroughPageDidChange(pageNumber:Int) // Called when current page changes
}
#objc class BWWalkthroughViewController: UIViewController, UIScrollViewDelegate, ViewControllerDelegate {
// MARK: - Public properties -
weak var delegate:BWWalkthroughViewControllerDelegate?
var currentPage:Int{ // The index of the current page (readonly)
get{
let page = Int((scrollview.contentOffset.x / view.bounds.size.width))
return page
}
}
// MARK: - Private properties -
let scrollview:UIScrollView!
var controllers:[UIViewController]!
var lastViewConstraint:NSArray?
var shouldCancelTimer = false
var aSound: AVAudioPlayer!
var isForSound: AVAudioPlayer!
var alligatorSound: AVAudioPlayer!
var audioPlayer = AVAudioPlayer?()
var error : NSError?
var soundTrack2 = AVAudioPlayer?()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var audioPlayerAnimalSound = AVAudioPlayer?()
var audioPlayerAlphabetSound = AVAudioPlayer?()
var audioPlayerFullPhraze = AVAudioPlayer?()
var audioPlayerPhonics = AVAudioPlayer?()
Code removed to save space carries on:
/**
Update the UI to reflect the current walkthrough situation
**/
private func updateUI(){
// Get the current page
pageControl?.currentPage = currentPage
// Notify delegate about the new page
delegate?.walkthroughPageDidChange?(currentPage)
}
receiver class
class BWWalkthroughPageViewController: UIViewController, BWWalkthroughPage, ViewControllerDelegate, BWWalkthroughViewControllerDelegate {
Function in second Class.
func walkthroughPageDidChange(pageNumber: Int) {
println("current page 2 \(pageNumber)")
}
walkthroughPageDidChange does work in the viewController class however. Please can you help me see what is wrong?
Your weak var delegate:BWWalkthroughViewControllerDelegate? is an optional variable and it is not assigned anywhere in your presented code, hence it will be nil. You have to instantiate it somewhere for it to work.
A good way to do so is:
class BWWalkthroughViewController {
let bwWalkthroughPageViewController = BWWalkthroughPageViewController()
var bwWalkthroughViewControllerDelegate : BWWalkthroughViewControllerDelegate!
init() {
bwWalkthroughViewControllerDelegate = bwWalkthroughPageViewController as BWWalkthroughViewControllerDelegate
}
}
A thing to note is that it is often good practice to name the delegates with meaningful names. The keyword "delegate" is often used for many classes and is restricted. A more verbose name will eliminate the chance of overriding an original delegate and will help identify each one if you have more than one.

Howto pass NSManagedObject via protocol/delegate?

I have a managed object defined:
#objc (Game)
class Game: NSManagedObject {
#NSManaged var player1: Player
#NSManaged var player2: Player
#NSManaged var totalScore: NSNumber
#NSManaged var gameDate: NSDate
}
I initialize it from ViewControllerA, then I give it to ViewControllerB using the delegate pattern. The protocol looks like this:
protocol gameProtocol {
func gameFunction(input: Game)
}
ViewControllerB signs up for the protocol:
class ViewControllerB: UIViewController, gameProtocol {...}
and implements this function to conform:
func gameFunction(input: Game) {
let currentGame = input
}
Then ViewControllerA can send a Game object to VCB as follows:
var gameDelegate: gameProtocol!
gameDelegate.gameFunction(myInitializedAndSavedGameObject)
This all works, but I need a class level variable inside ViewControllerB so that other code can be written to depend on the game. This, of course, does not work:
var currentGame = Game()
func gameFunction(input: Game) {
currentGame = input
}
I don't know the right words for it, but I think I want an initialized, empty Game object. I suppose I could write a convenience init that makes a temporary Game, but that doesn't seem like a good idea.
My current workaround is to have an NSManagedObjectID() and then recreate the object from the ID. But this is a lot of repeated code to get at an object that is central to what this ViewController is designed to work with.
So you want to push your NSManagedObject to your second View Controller?- You dont need an delegate, you could send your object as instance variable, as Wain already said.
For Example (in your MainViewController)
class MainViewControntroller: UIViewController {
var currentGameObject:YOURNSMANAGEDOBJECT!
func viewDidLoad() {
// load your Object
var fetchRequest = NSFetchRequest(entityName: "Game")
.....
games = context.executeFetchRequest(fetchRequest, error: nil) as [YOURNSMANAGEDOBJECT]
if(games.count > 0) {
// set to any game object (here is only the last)
currentGameObject = games.last?
}
}
}
// Initialize your Second View Controller (for example when using segues)
if(segue.identifier == "yourIdentifierInStoryboard") {
var yourNextViewController = (segue.destinationViewController as yourNextViewControllerClass)
yourNextViewController.currentGameObject = currentGameObject
So you are able to use your NSManagedObject in your SecondViewController - if you want to push it back, you could use a delegate.
'lazy var currentGame = Game()' seems to be what I want. By making the var lazy, the designated initializer is never incorrectly called. I am certain that the first thing to touch the var will be the gameFunction method, so my other code will compile and it won't crash at runtime. Alternative suggestions welcome.

Resources