Create a member for the UIViewController class - ios

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.

Related

How to subclass with base class data retain in Swift?

How i can pass data from First screen to Second screen provided that must use inheritance and data entered in base class should be available in second screen after push.
class FirstViewController {
var dataArray = [CustomModel]()
//methods will manupulate data
}
Then push subclass SecondViewController from class FirstViewController
class SecondViewController : FirstViewController {
print(dataArray)
}
Is this possible ? any solution to this ? I just wanted reuse most of code in many screens. Any help.
When you inherit class B with parent class A, then you can access its data members.
Here check this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//call show function of class B which is inherit from class A
let classB = B()
classB.showData()
}
}
class A {
var dataArray: [String] = ["ABC", "123", "XYZ"]
}
class B: A {
func showData()
{
print(dataArray)
}
}
Output:
["ABC", "123", "XYZ"]
This is basic rule of inheritance, is this what you are looking for! if not kindly explain your problem in detail.
P.S
you can also use static data members they are accessible without making object.

Is it pssible to use generic protocols in viewcontrollers in Swift?

I want to create a generic TableViewController for selecting any data that conforms to Equatable protocol, so when I need to have a ViewController to select some equatable data, I just create a ViewController that inherits the generic TableViewController.
What I'm thinking is:
Create a generic tableviewcontroller
// Example of any Equatable data types
struct Student: Equatable {
...
}
class GenericDataSelectionViewController<T: Equatable>: UITableViewController {
var items: [T] = []
...
}
and write all other basic codes for displaying and selceting data, so when I need a viewcontroller to select Student data, I just create a new viewcontroller:
class SelectStudentViewController: GenericDataSelectionViewController<Student>
When I need to call this vc to select a student, I just do
class fooViewController: UIViewController {
...
func selectStudent() {
let destVC = SelectStudentViewController()
destVC.items = students
// show destVC ...
}
...
}
But in order to get the selected data, I also need a generic protocol, so I create a protocol:
protocol DataSelectionProtocol: class {
associatedtype T: Equatable
func didSelect(_ option: T)
}
So in the generic vc, I create a variable
weak var delegate: DataSelectionProtocol?
and inside the selectStudent() function above, I add destVC.delegate = self and implement fooViewController to conform to DataSelectionProtocol and use didSelect function to handle the selected student data.
But I get a Protocol 'DataSelectionProtocol' can only be used as a generic constraint because it has Self or associated type requirements error when a add the delegate variable in generic tableviewcontroller.
So is this the correct way to do it? Or I need to create another protocol that conforms to DataSelectionProtocol for selecting student (but I feel this is not the correct way as I would have to create new protocols for new data types, and this is not so 'Generic')?

Create an array of objects that implements a specific protocol

TL;DR
I'm looking for an array type (var array = [TheTypeImLookingFor]()) like 'all objects that subclasses UIViewController and implements the protocol MyProtocol.
Explanation
I'm building a kind of wizard view with a container view and embedded child views (controller). No problem, this will work as long, as I have only one base type of child view controllers.
Due to the content of screens, I have now a bunch of view controllers of type MyTableViewController which is a subclass of UITableViewController and other view controllers that have regular UIViewControllers as base.
All of the view controllers have one thing in common. A default data property myData: MyObject.
I created a protocol MyProtocol that contains this property.
Now, I have to combine all this view controllers into one array to use it as wizard steps. As long as I only have to access the view controller methods (array items are type of UIViewController) I'm able to use var viewControllers = [UIViewController]() or if I wanna only access the myData property, I change the array item type to MyObject.
But the problem is, I have to access the methods from the UIViewController and from the protocol.
That's why I'm looking for an array type like 'all objects that subclasses UIViewController and implements the protocol MyProtocol.
I tried:
var viewControllers = [UIViewController: MyProtocol]() // is a dict
`var viewControllers = UIViewController where MyProtocol
`var viewControllers = UIViewController.conforms(to: MyProtocol)
...
But nothing works as expected.
As far as I know, there's currently no way to type something so that it describes anything which inherits from a given class and conforms to a given protocol.
One possible hacky workaround would be to just create a wrapper type in order to perform typecasting for you in the case that you need to treat the instance as a MyProtocol.
struct MyProtocolViewController {
let base: UIViewController
init<T : UIViewController>(_ base: T) where T : MyProtocol {
self.base = base
}
func asMyProtocol() -> MyProtocol {
return base as! MyProtocol
}
}
Now you can create a [MyProtocolViewController], and can either treat an element as a UIViewController, or a MyProtocol.
// given that ViewController and AnotherViewController conform to MyProtocol.
let viewControllers = [MyProtocolViewController(ViewController()),
MyProtocolViewController(AnotherViewController())]
for viewController in viewControllers {
print(viewController.asMyProtocol().myData)
print(viewController.base.prefersStatusBarHidden)
}
You could use protocol composition with a placeholder protocol for the class:
protocol UIViewControllerClass {}
extension UIViewController: UIViewControllerClass {}
protocol MyProtocol:class {}
class MySpecialVC:UIViewController,MyProtocol {}
var viewControllers = [UIViewControllerClass & MyProtocol]()
viewControllers.append( MySpecialVC() )
This covers the type safety part but doesn't let you access UIViewController methods without type casting. You can reduce the type casting ugliness by adding a typed property to your protocol (when it applies to the base class)
extension MyProtocol where Self: UIViewControllerClass
{
var vc:UIViewController { return self as! UIViewController }
}
// accessing the view controller's methods would then only require insertion of a property name.
viewControllers.first!.vc.view
Alternatively, you could define the UIViewController methods you need to call in the placeholder protocol but that could quickly become tiresome and redundant if you're going to use many of them.
Why not simply create :
Why not creating :
class ObservingViewController : UIViewController, MyProtocol {
}
var viewControllers : [ObservingViewController] = []
You can also create a protocol that defines all the UIViewController functions that you need. Make sure that you copy the method signature, otherwise you will have to implement the functions again.
protocol UIViewControllerInteractions {
//copy the signature from the methods you want to interact with here, e.g.
var title: String? { get set }
}
Then, you can extend your existing protocol.
protocol MyProtocol: UIViewControllerInteractions { }
Or create a new protocol that extends UIViewControllerInteractions and MyProtocol.
protocol MyProtocolViewController: UIViewControllerInteractions, MyProtocol { }
Now, when you extend your SubclassUIViewController, you still only have to add your myData because the methods in the UIViewControllerInteractions are already implemented by UIViewController (that's why we copied the method signature)
class SubclassUIViewController: MyProtocol {
var myData ...
}
You can now have an array of MyProtocol or MyProtocolViewController and also call the methods defined in UIViewControllerInteractions which will call the UIViewController methods.
var viewController: [MyProtocol] = [...]
viewController.forEach { (vc) in
print(vc.myData)
print(vc.title)
}
I had a similar issue and solved it with a custom base class. Imagine an array like:
var viewControllers: [MapViewController]
which all should extend from UIViewController and implement the following protocol:
protocol MapViewControllerDelegate {
func zoomToUser()
}
Then I've declared a base class like:
class MapViewController: UIViewController {
var delegate: MapViewControllerDelegate?
}
Caution: this class doesn't implement the above protocol but holds a property which provides the desired functionality. The next step is to define one of the UIViewController that will be added to the array:
class GoogleMapsViewController: MapViewController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension GoogleMapsViewController: MapViewControllerDelegate {
func zoomToUser() {
// Place custom google maps code here
}
}
The important part is located in the viewDidLoad method. The view controller assigns itself as the delegate.
Usage:
let googleMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GoogleMapsViewController") as! GoogleMapsViewController
let mapboxMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapboxMapsViewController") as! MapboxMapsViewController
let mapViewControllers: [MapViewController] = [googleMapsViewController, mapboxViewController]
for mapVC in mapViewControllers {
mapVC.delegate?.zoomToUser()
}
The benefits:
The MapViewController is like an abstract class and If I change the MapViewControllerDelegate the compiler forces me to implement the changes in the GoogleMapsViewController and in the MapboxMapsViewController.
If I need a second protocol I could just implement a second delegate property.
No type casting needed like in the other answers. Each UIViewController is still a UIViewController and provides all its methods.

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)

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