Swift, how to declare methods requiring instance members while following MVC - ios

I'm trying to use the MVC in an app loading a url into a webView. I'm struggling with how to/whether to define the back, forward, reload, etc... functions in the model or the viewController. I'm pretty sure they belong in the model, but then how do I call them in the IBAction for the corresponding button?
In trying to call the class function in the IBAction, first I have to create an instance of WebViewLoadRequest in each IBAction which seems extraneous. I can't (and probably shouldn't) create a global instance of WebViewLoadRequest because self isn't available in the property initializer to reference the UIWebView Outlet
class WebViewLoadRequest {
var outlet: UIWebView
var url : String
private var address: URL
init(outlet: UIWebView, url: String) {
self.outlet = outlet
self.url = url
self.address = URL(string: url)!
}
func load() {
self.outlet.loadRequest(URLRequest(url:address))
}
func back() {
if outlet.canGoBack {
outlet.goBack()
}
}
func forward() {
if outlet.canGoForward {
outlet.goForward()
}
}
func refresh() {
outlet.reload()
}
}
To simplify:
How can I write the following function in the model layer and implement it in the ViewController?
//outlet refers to a UIWebView
func back() {
if outlet.canGoBack {
outlet.goBack()
}
}
Or should this strictly be a function of the view controller? I realize essentially just the view is being changed, but there's a lot going on behind the scenes in the goBack operation that I'm not sure belongs in the ViewController

You are confusing two things here IMO, there is object oriented programming which you described in your question. There is also another concept which is commonly used and also very common in Swift, this concept is MVC. Model, View, Controller. This is basically a seperation of powers. In practice this comes down to:
Model - custom class with all the logic, in your case the WebViewLoadRequest
View - defined in a 'storyboard'
Controller - Will be a subclass of UIViewController, this subclass also has a reference to your model, all the IBActions and IBOutlets to connect to and update your UI.
You can learn more about MVC here:
https://www.raywenderlich.com/132662/mvc-in-ios-a-modern-approach
Also I would strongly recommend watching the C193p course from Standford by Paul Hegarty, this teaches you all there is to know. For example the 4th week is about MVC
https://itunes.apple.com/us/course/developing-ios-10-apps-with-swift/id1198467120
EDIT:
Basically your construction is a construction of delegation. The normal way to solve this issue is by creating a delegate property on your model-class, the delegate implements a certain protocol with for example a 'canIGoBack'-function. The model can request extra data from the delegate by calling that delegate function. Apple has something about delegation in their swift manual (check the 'Delegation' part:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
I believe Paul Hegarty also discussed it.
However, in this case it is also fine to do the canGoBack in the viewcontroller and conditionally call your model. MVC is only a guideline, there are many reasons to sometimes do logic in a viewcontroller, the biggest reason is probably if it makes shit easier. Implementing delegation only for a canGoBack is most of the times just silly.

Related

How can I utilize dependency injection in Swift with minimal reliance on external libraries?

I am trying to understand how dependency injection works in Swift so I can inject into my ViewController. I have some experience with it in Kotlin but am unsure of how to approach it in Swift. I would prefer to have minimal reliance on external libraries but am not opposed to using one or two if they are reliable and well-maintained. Here is some code I found on a blog post about DI in Swift.
import UIKit
class ViewController: UIViewController {
lazy var requestManager: RequestManager? = RequestManager()
}
// Initialize View Controller
let viewController = ViewController()
// Configure View Controller
viewController.requestManager = RequestManager()
I understand that instead of instantiating RequestManager in the ViewController, I can create an instance of the ViewController itself and instantiate it using property access but I don't understand where I should declare the instance of the ViewController. I feel like I am missing some code or some context. Can someone help me to understand this?
To quickly answer the exact question asked, of how to pass in variables to VC's inside a storyboard. You can simply do something like this in the previous viewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let vc = segue.destination as? SomeViewController else {
return
}
vc.requestManger = SomeRequestManger()
}
To give a longer, more opinionated answer:
I personally only use DI in very small and very limited ways. There are a few exceptions where more advanced use cases crop up. But in general I feel like a lot of people way over think this and just do it because they "should" as oppose to actually needing too. A lot of cases I see online of creating mock instances of every service class, and passing them around where needed, is simply a bad idea as you wind up testing very little of your actual code, and create a tonne of extra effort for yourself for no reason.
For an app i'm working on, all I did was create a mock instance of URLProtocol and hold onto a reference of a separate URLSession instance in a class in my test bundle, something like this:
public let mockURLSession: URLSession
private init() {
let sessionConfig = URLSessionConfiguration.ephemeral // Uses no caching / storage
sessionConfig.protocolClasses = [MockURLProtocol.self]
mockURLSession = URLSession(configuration: sessionConfig)
...
}
and then I had a DependencyManger.swift that was a singleton. Any Service class that needed a URLSession, was created and held inside that class, and I had a function to pass the mock session or the real session into each of those classes, from the manager.
From then on I didn't have to worry about adding 20K lines of code of third party libraries, just so I could have fancy Init's on my VC's. Or worry about writing hundreds of lines of code passing references between one viewController to another. Inside each VC I would just do:
DependencyManger.shared.networkService.get(...) {
...
}
All I need to do then is flip the toggle while running my tests, and all my networking code will use my MockURLProtocol, where I can override the methods and have it return stubbed data per URL. In this situation I only needed to mock my networking data, if you need to also mock push notifications for example, you'd need to go one step further.
Incredibly simple, works with interface builder or UI written in code, extremely flexible, a tiny amount of code, no third party dependencies to maintain

Best way to avoid singleton

For our iOS programming class we must make a framework for Swift iOS. We had the idea of a framework simplifying CoreData manipulation. I began by creating a class where you put the NSManagedObjectContext created in AppDelegate at the beginning, so you don't have to write this long (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext anymore.
open class SimpleCoreData {
var context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
func delete(entity: NSManagedObject) /*-> Bool*/ {
// some code
}
func getAll(entityClass: NSManagedObject.Type) throws -> [NSManagedObject]? {
// some code
}
func create(entityDescr: NSManagedObject.Type) -> NSManagedObject? {
// some code
}
But I would like it to be accessible from everywhere in the application, and this simplification would be useless if you have to instantiate this each time.
I was first thinking about a singleton, but I recently learned it wasn't a good practice.
So do you know any solution to make this accessible from everywhere in the client app? Or maybe singleton is okay in this case?
Keeping Rob Napier's excellent comments in mind, if you decide to avoid a singleton in this case, a common approach would be
Create an instance of your SimpleCoreData class in the app delegate when the app launches.
Have the app delegate pass this to your initial view controller. That view controller would have a SimpleCoreData property but would not create the instance-- it would expect one to be assigned by whichever code creates it, which here is the app delegate.
Repeat this pattern everywhere you need a SimpleCoreData. That is, when you create an object that needs a SimpleCoreData, make sure it has a property with that type, and assign a value when you create it. For view controllers, a good place to do this is in prepare(for:sender:), if you're using segues.
It's not necessary to create the SimpleCoreData in the app delegate, though. You could create it at the first point in the app hierarchy where it's needed, and pass it along from there. So if it's only needed in the second view controller in the hierarchy and in other objects loaded from there, create it in that view controller.
This doesn't make your SimpleCoreData instance available everywhere automatically, it means that you're creating one and then passing it around. Often that works fine, but as Rob notes it's not always the best approach. It can lead to passing an object along to an object that doesn't need it, because some other object that gets created later on does. For example if you need SimpleCoreData in your initial view controller but then not again until five levels down the hierarchy, you still need to pass it along every step of the way. That's an example of when a shared instance can be useful. It's not a singleton since other instances are allowed, but it's a default instance that can be used as needed.
I finally learned thanks to you that singletons aren't so evil, they can be used in some case, including this one, and it seemed in my case that it was a good choice. Maybe I will change it for a shared instance pattern.
So the singleton works well. Thank you everybody for your advices, I learned a lot in design patterns.

Passing information between controllers in a Swifty (/protocol) way?

I am trying to pass information from controller A to controller B. The thing is, I want:
Concise: To minimize the autocomplete from XCode to some key information. I want to know in a easy way the exact parameters required before pushing the controller on the stack.
Avoid segues. From my understanding, segues create a lot of tight coupling in the storyboard. I don't want to be dependent of the storyboard to pass information. (Each time I want to switch controller A to another, I have to go to the storyboard to make some changes). I might split my app in several storyboards at some point, and segues can be quite annoying to deal with.
Beautiful: Maybe Swift can provide a Swifty solution I have not thought about.
What I have been trying to accomplish at first was to push controller as protocol. Even if it not possible, let me explain:
Pushing a controller as a protocol would allow me to have exact visibility on my attributes.
There is no tight coupling related to the storyboard
Many controllers (A, C, D) might want to push a B controller, I might give them each one of them a different protocol to push controller B. Maybe B could appear under different circumstances.
At first, my code was looking like that (the storyboard extension works):
if let myBCustomVC = storyboard.instanciateControllerWithIdentifier(MyBCustomVC.self) as? myVCustomFromAProtocol{
myBCustomVC.AToB = self.data.count
self.navigationController?.pushViewController(myBCustomVC, animated: true)
}
protocol myVCustomFromAProtocol {
var AToB: Int {get set}
}
The thing is, I cannot push a view controller downcast to a protocol. I had to come by an ugly UINavigationController extension. Here is the full result.
if let myBCustomVC = storyboard.instanciateControllerWithIdentifier(MyBCustomVC.self) as? myVCustomFromAProtocol{
myBCustomVC.AToB = self.data.count
self.navigationController?.pushViewController(vcToPush: myBCustomVC, animated: true)
}
extension UINavigationController{
func pushViewController(vcToPush: Any, animated: Bool){
if let vc = vcToPush as? UIViewController{
self.pushViewController(vc, animated: animated)
}
}
}
Let's face it, while achieving my first two goals, I have downcasted an Any to a UIViewController, woosh.
Is there any way to avoid tight coupling and push controllers on the stack in a beautiful way while keeping a limited visibility on the parameters to pass from the first view controller(A) to the second(B). What are your thoughts? Why wouldn't I want to do that?
I'd approach this from the other side (i.e. the side of the controller you are presenting).
You could do something like creating a Presenter protocol.
protocol Presenter {
func present(inContext context: UIViewController)
// possibly with some data in it too (if it's across all instances)...
var count: Int {get set}
}
And have some sort of factory struct on top of each...
struct BVCPresenter: Presenter {
var count: Int = 0
func present(inContext context: UIViewController) {
// create the BViewController here
let controller = ...
context.push(controller, animated: true)
}
}
Or something along those lines. It really depends on the use cases and the data being passed around etc...
So now A can do...
someGenericPresenter.present(inContext: navigationController)
The generic presenter can be passed in as a property to A or can be passed into a function as a parameter etc...
or something.
You could even create a "FlowController" that manages these transitions and data for you...
self.flowController.presentB()
The flowController then knows what B needs and how and where to present it and populate it with data. The individual view controllers don't actually need to know anything about each other.
I don't think there is a solution for this that would suit all cases. It requires a lot of thought and design. But there are many options. Think about what you do need between the view controllers and work from that.
Also, don't be too worried about creating something that "just works" even if it isn't textbook generic, "Swifty", elegant, beautiful code. Having code that works is much better than having a beautifully designed system that doesn't.
:)
With the help of Fogmeister I came up with this answer. It has several advantages and disadvantages over the use of a Presenter Object that inherits from a protocol.
Solution: Use a static factory method (or several depending of the use case) located in the controller to be instantiated.
Advantages:
Using a static factory method enforces to pass parameters that the actual controller know to be required for his own initialization.
No structure involved (less complexity?)
The constant use of a static factory method becomes natural over time. You know when looking at a controller you must implement the method to call it, and you know the parameters required. You do not have to worry which parameters must be implemented to push the controller, just implement the function. Using a presenter, you do not have this knowledge as the unwrapped parameters can be numerous.
The use of a Presenter is closer to MVP and gets away from the dependency injection model advocated by Apple (MVC)
Disadvantages:
The use of the Presenter pattern suggested by Fogmeister uses a protocol. The protocol gives limited visibility to controller A (and all controllers that want to push a controller B).
The use of the Presenter pattern gives more modularity and reduces tight coupling. Controller A has no need to know anything about controller B. The replacement of controller B to another controller C can be done without affecting all the controllers that were going to B (can be quite useful if you are restructuring your application or in some use cases).
Here is my current implementation in my class:
In controller B (static function):
static func prepareController(originController: UIViewController, AToB: Int) -> bVC? {
if let bVC = originController.storyboard?.instanciateControllerWithIdentifier(self) as? bVC{
bVC.AToB = AToB
return bVC
}
else{
return nil
}
}
In controller A (to push the controller on the stack):
if let bVC = bVC(originController: self, AToB: *someValue*){
self.navigationController?.pushViewController(bVC, animated: true)
}
Solution has to be taken with a grain of salt, let me know what you think #Fogmeister?
Both solutions can be combined. Using a protocoled structure that would use the static method provided by the controller. The thing is it can quickly become overly complex for people you introduce to the project. That's why I am sticking to my solution at the moment. I am looking at frameworks for dependency injection though.

Swift custom protocol which calls more than one function

I have a login view controller. Once the login is done, it dismisses itself and calls a function in another view controller. And in that function there are four different functions that do different actions. The problem is the protocol calls 4 functions at the same time. So is there a way to call a specific function inside the other function? like;
func mainfunction(){
funcOne()
funcThree()
funcTwo()
funcOne()
}
So instead of calling all, I want to call one. And mainFunction is a part of protocol.
Thanks
Just extend your protocol. Instead of having one mainFunction, the protocol would have all four.
#protocol YourProtocol {
func funcOne()
func funcTwo()
func funcThree()
func funcFour()
}
If you add the separate functions in the protocol declaration, you will be able to call them individually if they exist.
The protocol serves as a contract of what should be implemented. It gives the compiler and developers a way of knowing what is supposed to be available. It's very similar to the concept of declaring functions ahead of time in a C header file. The functions are not available until you or someone else implements them.

How to use the object oriented language Swift the right way?

I'm new in Swift and even in object oriented programming languages itself. So my question is, how to use this very extensive language Swift the right way? I give an example to verify my problem:
Let's say I have got two classes:
Class ScanForBluetoth{} //Handles all parts to scan for BT devices
class ScanForDevices: UIViewController, CBCentralManagerDelegate , CBPeripheralDelegate, UITableViewDelegate,UITableViewDataSource{}
Class Bluetooth{} //Handles only the Bluetooth parts with it's delegates
class Bluetooth: ScanForDevices{}
Now, I would like to implement all my used delegates and other Bluetooth specific functions into the Bluetooth class. BUT I need some objects (for example of the CBCentralManagerDelegate) in my ScanForDevices class, too. So, I have to implement all my delegates in my "mother" class ScanForDevices although I only need some properties. SO if I implement the delegates, I have to be conform with the protocol and must implement ALL my needed delegate functions... At the end I have implemented all my delegate functions in ScanForDevices and then override them in Bluetooth. But I don't think that my way is the best way to realize this problem...
Thanks for reading!
Firstly, I would like to point out that your naming conventions are really off. In object oriented programming, you want your class names to be objects (nouns). You named your classes by what they were doing, rather than what they are. A better name choice for your classes would be something like BluetoothDeviceScanner, rather than scan for devices, and BluetoothManager rather than the non-explicit "bluetooth".
Secondly, what you have done is subclassed the bluetooth class to scan for devices class, which causes it to inherit all the functionality of its class. This really doesn't make any sense. Subclassing is used to create an object based on a parent object, while these two objects handle two totally different things, and then you're planning on overriding the functions anyway. Instead of that, you should just include the protocols that you need in the bluetooth class separately. Keep the functionality of the two classes separated as much as possible.
Thirdly, you should separate your view controller functionality from the scanning functionality. What I mean is the "ScanForDevices" object's job is to scan for devices, so it shouldn't also have the job of controlling a view... I would remove the UIViewController protocol and introduce a new view controller class, and within that class you can have a property that is assigned the "ScanForDevices" object, at which point the devices can be scanned for within the viewcontroller, but the scanning functionality is contained within a single object (which is best practice).
EDIT
All you need to do to "connect" the data is have your BluetoothManager and BluetoothScanner objects is have them available as a property within whatever view controller you need them. So, in the viewcontroller declare some properties, I usually do it with optionals so that I don't have to worry about initializing the properties (This means you need to unwrap the variables before using them).
In your ViewController...
var bluetoothScanner: BluetoothScanner?
var bluetoothManager: BluetoothManager?
override func viewDidLoad() {
super.viewDidLoad()
bluetoothScanner = BluetoothScanner(init parameters)
bluetoothManager = BluetoothManager(init parameters)
}
You're objects are now "connected" in the sense that you have access to them and all their properties/methods in the viewcontroller. Now that I think about it, you don't even need to have both objects on this level. You can store the BluetoothScanner as a property of the Bluetooth manager, at which point you would only need to use a BluetoothManager object to handle all your bluetooth needs on the view controller level.
Init Methods
//init method that takes 2 parameters, a string and a uiviewcontroller.
init(param1: String, param2: UIViewController) {
//Struct init code using parameters
self.name = param1
self.viewController = param2
}
//init method that takes no parameters, but still initializes the same properties.
init() {
self.name = "YungGun"
self.viewController = UIViewController()
}
Keep in mind these initialization methods are made up and have nothing to do with your problem at hand, I was attempting to illustrate that this is where you define the parameters needed to initialize the struct. The same parameters in the parenthesis must be passed when creating an instance of the struct.

Resources