I'm creating an app and I have all the logic done, but I want to do a Code refactoring and create MVC pattern. But I dealing with some asynchronous informations, that came from API.
/MenuViewController
Alamofire.request(.GET, Urls.menu).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
var product: [Product] = []
for (_, subJson): (String, JSON) in data {
product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
}
dispatch_async(dispatch_get_main_queue()) {
self.products += product
self.tableView.reloadData()
}
}
}
}
This is my code, already working. But I want to create a Model that will handle this and just return the array of Products to my MenuViewController.
Model/Menu
class Menu {
var products: [Product] = []
init() {
Alamofire.request(.GET, Urls.menu).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
var product: [Product] = []
for (_, subJson): (String, JSON) in data {
product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
}
dispatch_async(dispatch_get_main_queue()) {
self.products += product
}
}
}
}
}
func totalOfProducts() -> Int {
return self.products.count
}
func getProducts() -> [Product]? {
return self.products
}
func getProductFromIndex(index: Int) -> Product {
return self.products[index]
}
}
But I got my self thinking, how I gonna get the main_queue to another class?
So I tried something like this:
class MenuViewControlvar: UITableViewController {
var products: [Product] = []
let menu: Menu = Menu()
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
if let products = menu.getProducts() {
self.tableView.reloadData()
}
// rest of the code
But didn't worked. My TableView is never updated.
I was wondering if I can do this, or I've to keep my Alamofire code in my viewDidLoad() from my MenuViewController
Thank you.
I am just giving you a direction with the step I would follow (Not writing the code thinking you can work it out):
First, write a networking class that accepts network request along with a competition block. Completion block shall be executed as soon as networking is done. This is a wrapper class and can be used across classes.
Second, write a model class that has all the parameters necessary for view controller's functionalities/view drawing.
Third, from view controller, call the networking class. In completion block, pass the model setting, table reload code and any code to remove loading overlay/indicator. This block should get executed on main queue.
Fourth, add code to show loading overlay/indicator before you trigger networking.
Delegation is an ideal solution for this problem of updating your model data and your view based on an asynchronous network call and it’s pretty much the same technique that is implemented throughout the iOS SDK to solve the same problem. There are many benefits of delegation over observation, another viable solution.
First, move your networking code to a separate class
class NetworkingController {
Create a protocol that view controllers can conform to. This provides the loose coupling between your network operations and your views to effectively maintain separation between the MVC layers.
#protocol NetworkingControllerDelegate: class {
func menuDataDidUpdate()
}
Have the networking controller support a property for its delegate
weak var delegate: NetworkingControllerDelegate?
In summary your networking class now looks something like this:
#protocol NetworkingControllerDelegate: class {
func menuDataDidUpdate()
}
class NetworkingController {
weak var delegate: NetworkingControllerDelegate?
// Insert networking functions here.
}
Then, have your view controller conform to this protocol like so
class MenuViewController: NetworkingControllerDelegate {
and create a new network controller in your view controller
var myNetworkController = NetworkController()
and set the delegate of your network controller instance to be your view controller
myNetworkController.delegate = self
Then in your networking code, when the network request has completed and your model has been updated, make a call to the networking controller's delegate.
delegate.menuDidUpdate()
Create the implementation for this method in your view controller since it is now the delegate for your networking code.
func menuDidUpdate() {
// Update your menu.
}
This makes your view controller look something like:
class MenuViewController: NetworkingControllerDelegate {
var myNetworkController = NetworkController()
override func viewDidLoad() {
myNetworkController.delegate = self
}
// MARK: NetworkingControllerDelegate
func menuDidUpdate() {
// Update your menu.
}
}
This is just the outline of the implementation to give you the necessary information about how to proceed. Fully adapting this to your problem is up to you.
Related
I am new to Swift and am building an app to learn. Right now I am making the registration section of the app.
I thought the UX would be better if there were multiple VC's asking a single question, i.e. one for your name, one for your birthdate, etc as opposed to jamming all that into a single view controller. The final view controller collects all of that information and sends a dictionary as FUser object to be saved on Firebase.
I figured I could instantiate the final view controller on each of the previous five view controllers and pass that data directly to the end. I kept getting errors and figured out that the variables were nil. It works just fine if I pass the data directly to the next view controller but it doesn't seem to let me send it several view controllers down. Obviously there's a nuance to how the memory is being managed here that I'm not tracking.
Is there a way to do what I am trying to do or do I have to pass the data through each view controller along the way?
import UIKit
class FirstViewController: UIViewController {
//MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - IBActions
#IBAction func continueToMiddleViewController(_ sender: Any) {
let vcFinal = storyboard?.instantiateViewController(withIdentifier:
"finalVC") as! finalViewController
vcFinal.firstName = firstNameTextField.text
let vc = storyboard?.instantiateViewController(withIdentifier:
"middleVC") as! middleViewController
vc.modalPresentationStyle = .fullScreen
present(vc, animated: false)
}
...
}
import UIKit
class FinalViewController: UIViewController {
var firstName: String?
...
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
...
}
TL;DR: The fastest one that would solve your problem is creating a singleton
There are many strategies for this. For a starter, it might be a good idea to read some begginer articles, like this one. I can update this answer if you don't find it useful, but it'd look just like the article
Viewcontroller's variable can't be initiated until any of the init method is called.
There are detailed answers on this thread.
Passing Data between ViewControllers
Another way to approach this problem could be to make use of closures. Note that personally I've moved away from using storyboards but I'll try to explain still. Closures are also referred to as callbacks, blocks, or in some context like here - completions.
You can declare a closure like let onSubmitInfo: (String?) -> Void below, it stores a reference to a block of code that can be executed at a later stage just like a function and it takes an optional string as a parameter just like a function can.
The closures are specified in the initialisers where a block of code is passed into the respective classes below and the closures are then called in the IBActions that will trigger the block of code that is defined where the below classes are initialised:
class First: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(firstNameTextField.text)
}
}
class Second: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var lastNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(lastNameTextField.text)
}
}
To manage showing the above views and collecting the values returned by their closures (i.e. onSubmitInfo) we create a FlowController class that will also show the next view when the closure is called.
In FlowController we define the closures or blocks of code to be executed when it is called inside the IBAction in the respective First and Second classes above.
The optional string that is provided in the respective First and Second classes is used as the (firstName) and (secondName) closure properties below:
class FlowController: UIViewController {
private var fistName: String?
private var lastName: String?
...
private func showFirstView() {
let firstViewController = First(onSubmitInfo: { (firstName) in
self.firstName = firstName
showSecondView()
})
navigationController?.pushViewController(
firstViewController,
animated: true)
}
private func showSecondView() {
let secondViewController = Second(onSubmitInfo: { (lastName) in
self.lastName = lastName
showFinalView()
})
navigationController?.pushViewController(
secondViewController,
animated: true)
}
private func showFinalView() {
let finalViewController = Final(
firstName: firstName,
lastName: lastName)
navigationController?.pushViewController(
finalViewController,
animated: true)
}
}
The FlowController finally shows the Final view controller after it has collected the firstName form the First view controller and the lastName form the Second view controller in the showFinalView function above.
class Final: UIViewController {
let firstName: String
let lastName: String
...
}
I hope this is a shove in the right direction. I have moved away from storyboards because I find creating views in code is more verbose and clear on peer reviews and it was also easier for me to manage constraints and just to manage views in general.
I have a view model that's being used in two flows and has gotten to the stage where it should really be split out into a super class and two subclasses. However, I'm getting confused as the best way to go about performing some subclassing.
On creation of the view model, I pass in all the interactions that could happen from the view like so:
View
class SomeViewController: UIViewController {
#IBOutlet private weak var nextButton: UIButton!
private var presenter: SomeViewModel!
override func viewDidLoad() {
super.viewDidLoad()
presenter.configure(nextButtonTapped: nextButton.rx.tap.asDriver())
}
}
Then I can handle these actions within my view model like so:
ViewModel
class SomeViewModel {
private let normalFlow: Bool
private let diposeBag = DisposeBag()
init(normalFlow: Bool) {
self.normalFlow = normalFlow
}
func configure(nextButtonTapped: Driver<Void>) {
handle(nextButtonTapped: nextButtonTapped)
// call to any other input handlers here...
}
func handle(nextButtonTapped: Driver<Void>) {
nextButtonTapped.drive(onNext: { [unowned self] in
guard self.safetyCheckOnePasses(), safetyCheckTwoPasses() else {
return
}
if normalFlow {
// do some set of actions
} else {
// do another set of actions
}
}).disposed(by: disposeBag)
}
func safetyCheckOnePasses() -> Bool {
// perform some sanity check...
return true
}
func safetyCheckTwoPasses() -> Bool {
// perform another sanity check...
return true
}
}
I'm getting confused as to what the best way to override the handle(nextButtonTapped: Driver<Void>) is because I still want those sanity checks to happen at the start of the onNext for every subclass, but I want the body after that to be different for the different subclasses. What would be the best way to go about this without duplicating code?
Rx is part of the functional paradigm and as such, subclassing is not appropriate.
Move your safetyCheckOnePasses() and safetyCheckTwoPasses() functions out of the class (or at least make them static.) That way they can be reused without needing an instance.
I don't clear about these two, Nowadays the world is shifting to the closure types. But I'm not clearly understanding this. Can someone explain me with a real-time example?
So a real life example of both would be something like this:
protocol TestDelegateClassDelegate: class {
func iAmDone()
}
class TestDelegateClass {
weak var delegate: TestDelegateClassDelegate?
func doStuff() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.delegate?.iAmDone()
}
}
}
class TestClosureClass {
var completion: (() -> Void)?
func doStuff() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.completion?()
}
}
}
class ViewController: UIViewController, TestDelegateClassDelegate {
func iAmDone() {
print("TestDelegateClassDelegate is done")
}
override func viewDidLoad() {
super.viewDidLoad()
let testingDelegate = TestDelegateClass()
testingDelegate.delegate = self
testingDelegate.doStuff()
let testingClosure = TestClosureClass()
testingClosure.completion = {
print("TestClosureClass is done")
}
testingClosure.doStuff()
}
}
Here we have 2 classes TestDelegateClass and TestClosureClass. Each of them have a method doStuff which waits for 3 seconds and then reports back to whoever is listening where one uses delegate procedure and the other one uses closure procedure.
Although they do nothing but wait you can easily imagine that they for instance upload an image to server and notify when they are done. So for instance you might want to have an activity indicator running while uploading is in progress and stop it when done. It would look like so:
class ViewController: UIViewController, TestDelegateClassDelegate {
#IBOutlet private var activityIndicator: UIActivityIndicatorView?
func iAmDone() {
print("TestDelegateClassDelegate is done")
activityIndicator?.stopAnimating()
}
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator?.startAnimating()
let testingDelegate = TestDelegateClass()
testingDelegate.delegate = self
testingDelegate.doStuff()
activityIndicator?.startAnimating()
let testingClosure = TestClosureClass()
testingClosure.completion = {
self.activityIndicator?.stopAnimating()
print("TestClosureClass is done")
}
testingClosure.doStuff()
}
}
Naturally you would only use one of the two procedures.
You can see there is a huge difference in code. To do a delegate procedure you need to create a protocol, in this case TestDelegateClassDelegate. A protocol is what defines the interface of a listener. And since an iAmDone method is defined it must be defined in ViewController as well as long as it is defined as TestDelegateClassDelegate. Otherwise it will not compile. So anything declared as TestDelegateClassDelegate will have that method and any class can call it. In our case we have weak var delegate: TestDelegateClassDelegate?. That is why we can call delegate?.iAmDone() without caring what delegate actually is. For instance we can create another class:
class SomeClass: TestDelegateClassDelegate {
func iAmDone() {
print("Something cool happened")
}
init() {
let testingDelegate = TestDelegateClass()
testingDelegate.delegate = self
testingDelegate.doStuff()
}
}
So a good example for instance is an UITableView that uses a delegate and dataSource (both are delegates, just properties are named differently). And table view will call the methods of whatever class you set to those properties without needing to know what that class is as long as it corresponds to the given protocols.
Same could be achieved with closures. A table view could have been defined using properties giving closures like:
tableView.onNumberOfRows { section in
return 4
}
But that would most likely lead into one big mess of a code. Also closures would in this case be giving many programmers headaches due to potential memory leaks. It is not that closures are less safe or anything, they just do a lot of code you can't see which may produce retain cycles. In this specific case a most likely leak would be:
tableView.onNumberOfRows { section in
return self.dataModel.count
}
and a fix to it is simply doing
tableView.onNumberOfRows { [weak self] section in
return self?.dataModel.count ?? 0
}
which now looks overcomplicated.
I will not go into depths of closures but in the end when you have repeated call to callbacks (like in case of table view) you will need a weak link either at delegate or in closure. But when the closure is called only once (like uploading an image) there is no need for a weak link in closures (in most but not all cases).
In retrospective use closures as much as possible but avoid or use caution as soon as a closure is used as a property (which is ironically the example I gave). But you would rather do just this:
func doSomethingWithClosure(_ completion: #escaping (() -> Void)) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
completion()
}
}
And use it as
doSomethingWithClosure {
self.activityIndicator?.stopAnimating()
print("TestClosureClass is done")
}
This has now removed all potential risks. I hope this clears a thing or two for you.
In Swift/obj-c the term delegate is used to refer to a protocol that responds to specific selectors.
Thing about it just like calling a method on an object.
E.g.
protocol CalculatorDelegate : class { // : class so it can be made 'weak'
func onCalculation(result: Int) -> Void
}
Now if we have a Calculator class, to use the delegate we'd do something like
class Calculator() {
weak var delegate: CalculatorDelegate?
func calculate(_ a: Int, _ b: Int) -> Int {
let result = a + b
self.delegate?.onCalculation(result: result)
return result
}
}
Then in some other class (e.g. in iOS - a View Controller) we might do:
class MyClass : CalculatorDelegate {
func onCalculation(result: Int) {
print("Delegate method on calculation called with result \(result)")
}
func someButtonPress() {
let calculator = Calculator()
calculator.delegate = self
calculator.calculate(42, 66)
}
}
So you can see how the setup is quite elaborate.
Now closures are simply blocks of code that can be called in other places, so you could change all of the code like so:
class Calculator2() {
weak var delegate: CalculatorDelegate?
func calculate(_ a: Int, _ b: Int, onCalculation: (#escaping (Int) -> Void) -> Int)?) {
let result = a + b
onCalculation?(result)
return result
}
}
class MyClass {
func someButtonPress() {
let calculator = Calculator2()
calculator.calculate(42, 66, onCalculation: { (result: Int) in
print("Closure invoked with \(result)")
})
}
}
However, with closures you need to be aware that it is a lot easier to shoot yourself in the foot by capturing variables (e.g. self) strongly, which will lead to memory leaks even under ARC regime.
Closures are first-class objects so that they can be nested and passed around
Simply,
In swift, functions are primitive data types like int, double or character that is why you can pass a function in the function parameter. In swift mechanism simplify in closures syntax like lambda expression in other languages.
E.g. If you want to call rest API through URSSession or Alamofire and return response data then you should use completionHandler(it's closure).
Void closure : - {(paramter:DataType)->Void}
Return closure : - {(paramter:DataType)->DataType}
e.g. (int, int) -> (int)
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
I have VCs in an iOS app which have quite a lot of UI controls. I would now need to replace or "mock" some of these controls when in a specific state. In some cases this would be just disabling button actions, but in some cases the actions that happen need to be replaced with something completely different.
I don't really like the idea of having this sort of check littered all around the codebase.
if condition {
...Special/disabled functionality
} else {
...Normal functionality
}
In Android, I can just subclass each Fragment/Activity and build the functionality there, and then doing the if/else when inserting Fragments or launching activities.
But on iOS with Storyboards/IBActions and Segues, UIs and VCs are really tightly coupled. You either end up duplicating UI views or adding a lot of finicky code to already large VCs.
What would be the best way to handle this in iOS?
Sample code of what I want to avoid doing:
//Before:
class SomeViewController : UIViewController {
#IBAction onSomeButton() {
checkSomeState()
doANetworkRequest(() -> {
someCompletionHandler()
updatesTheUI()
}
updateTheUIWhileLoading()
}
#IBAction onSomeOtherButton() {
checkAnotherState()
updateUI()
}
}
//After:
class SomeViewController : UIViewController {
#IBAction onSomeButton() {
if specialState {
doSomethingSimpler()
} else {
checkSomeState()
doANetworkRequest(() -> {
someCompletionHandler()
updatesTheUI()
}
updateTheUIWhileLoading()
}
}
#IBAction onSomeOtherButton() {
if specialState {
return // Do nothing
} else {
checkAnotherState()
updateUI()
}
}
}
I'd suggest using the MVVM (Model - View - ViewModel) pattern. You pass the ViewModel to your controller and delegate all actions to it. You can also use it to style your views and decide if some of them should be hidden or disabled, etc.
Let's image a shopping app in which your pro users get a 10% discount and can use a free-shipping option.
protocol PaymentScreenViewModelProtocol {
var regularPriceString: String { get }
var discountedPriceString: String? { get }
var isFreeShippingAvailable: Bool { get }
func userSelectedFreeShipping()
func buy()
}
class StandardUserPaymentScreenViewModel: PaymentScreenViewModelProtocol {
let regularPriceString: String = "20"
let discountedPriceString: String? = nil
let isFreeShippingAvailable: Bool = false
func userSelectedFreeShipping() {
// standard users cannot use free shipping!
}
func buy() {
// process buying
}
}
class ProUserPaymentScreenViewModel: PaymentScreenViewModelProtocol {
let regularPriceString: String = "20"
let discountedPriceString: String? = "18"
let isFreeShippingAvailable: Bool = true
func userSelectedFreeShipping() {
// process selection of free shipping
}
func buy() {
// process buying
}
}
class PaymentViewController: UIViewController {
#IBOutlet weak var priceLabel: UILabel!
#IBOutlet weak var discountedPriceLabel: UILabel!
#IBOutlet weak var freeShippingButton: UIButton!
var viewModel: PaymentScreenViewModelProtocol
override func viewDidLoad() {
super.viewDidLoad()
priceLabel.text = viewModel.regularPriceString
discountedPriceLabel.text = viewModel.discountedPriceString
freeShippingButton.isHidden = !viewModel.isFreeShippingAvailable
}
#IBAction func userDidPressFreeShippingButton() {
viewModel.userSelectedFreeShipping()
}
#IBAction func userDidPressBuy() {
viewModel.buy()
}
}
This approach let's you decouple your logic from your views. It's also easier to test this logic.
One thing to consider and decide is the approach as to how to inject the view model into the view controller. I can see three possibilities :
Via init - you provide a custom initializer requiring to pass the view model. This will mean you won't be able to use segue's or storyboards (you will be able to use xibs). This will let your view model be non-optional.
Via property setting with default implementation - if you provide some form of default/empty implementation of your view model you could use it as a default value for it, and set the proper implementation later (for example in prepareForSegue). This enables you to use segues, storyboards and have the view model be non-optional (it just adds the overhead of having an extra empty implementation).
Via property setting without default implementation - this basically means that your view model will need to be an optional and you will have to check for it almost everytime you access it.
I have simple ListView that needs to display the records from the network layer. (first screen of the application)
I need to get some opinion as to which will be correct flow so that I can make the unit test cases easily. (No VIPER architecture)
NetworkMgr makes the network calls and create Model objects.
These Model objects needs to be populated in ListTableView.
I have a completion handler method to call the network request which give the model objects.
func getData() {
dataMgr.requestData(url: "String") { (EmployeesArray, error) in
// print(error)
}
}
Now the Question is - For unit testing when I am calling the ListDataTest since the ListVC is in storyboard when it loads the View the viewdidLoad method calls the which will initiate the network logic.
SO I am not able to test only the UI related stuffs.
I tried to create some extension in ListDataTest class but no success is achieved.
Below is the flow of the Controllers : -
===
class ListVC: UIViewController {
var dataProvider: ListData
override func viewDidLoad() {
super.viewDidLoad()
dataProvider.viewLoaded()
}
}
=======
In ListData class
protocol DatProviderLoad {
func viewLoaded()
}
class ListData: NSObject {
}
extension ListData : DatProviderLoad {
func viewLoaded() {
print("loaded")
//the network calls goes here
}
}
/—
The test class
class ListDataProviderTest: XCTestCase {
var sut: ListData!
var controller: ListVC!
override func setUp() {
super.setUp()
sut = ListData()
let storyBoard = UIStoryboard(name:"Main", bundle: nil)
controller = storyBoard.instantiateViewController(withIdentifier: "ListVC") as! ListVC
controller.dataProvider = sut //before this called the storyboard already called the viewdidload once
_ = controller.view
}
}
Your feedback will be very helpful.
Any hint or tutorial in right direction will be highly appreciable.
Lets try to do this in MVVM way.
Think ViewController as part of View layer.
To call the network layer and to convert models into view models introduce a ViewManager.
The Viewcontroller will ask ViewManager to provide the data(ViewModel) and passes all actions(like button press) to ViewManager to handle the business logic.
This way it will be easy to write test cases for ViewManager layer(which is supposed to have all the business logic) and your View is not coupled with either the Network layer or data models.