Thursday, February 2, 2023
Learning Code
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#
No Result
View All Result
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#
No Result
View All Result
Learning Code
No Result
View All Result
Home Swift

VIPER best practices for iOS developers

learningcode_x1mckf by learningcode_x1mckf
September 30, 2022
in Swift
0
VIPER best practices for iOS developers
74
SHARES
1.2k
VIEWS
Share on FacebookShare on Twitter


2019/03/11

On this tutorial I will present you a whole information about the best way to construct a VIPER primarily based iOS software, written totally in Swift.

VIPER

This submit is somewhat bit outdated, please anticipate a brand new model coming quickly…

Getting began with VIPER

To start with, it’s best to learn my earlier (extra theoretical) article about the VIPER architecture itself. It is a fairly respectable writing explaining all of the VIPER elements and reminiscence administration. I’ve additionally polished it somewhat bit, final week. ⭐️

The issue with that article nonetheless was that I have never present you the true deal, aka. the Swift code for implementing VIPER. Now after a full 12 months of tasks utilizing this structure I can lastly share all my greatest practices with you.

So, let’s begin by making a model new Xcode venture, use the one view app template, title the venture (VIPER greatest practices), use Swift and now you are able to take the subsequent step of constructing an superior “enterprise grade” iOS app.


Producing VIPER modules

Lesson 1: by no means create a module by hand, all the time use a code generator, as a result of it is a repetative activity, it is fuckin’ boring plus it’s best to concentrate on extra essential issues than making boilerplate code. You need to use my light-weight module generator known as:

VIPERA

Simply obtain or clone the repository from github. You may set up the binary software by working swift run set up --with-templates. This can set up the vipera app below /usr/native/bin/ and the fundamental templates below the ~/.vipera listing. You need to use your individual templates too, however for now I am going to work with the default one. 🔨

I often begin with a module known as Principal that is the foundation view of the applying. You may generate it by calling vipera Principal within the venture listing, so the generator can use the correct venture title for the header feedback contained in the template recordsdata.

Clear up the venture construction somewhat bit, by making use of my conventions for Xcode, which means assets goes to an Property folder, and all of the Swift recordsdata into the Sources listing. These days I additionally change the AppDelegate.swift file, and I make a separate extension for the UIApplicationDelegate protocol.

Create a Modules group (with a bodily folder too) below the Sources listing and transfer the newly generated Principal module below that group. Now repair the venture points, by deciding on the Data.plist file from the Property folder for the present goal. Additionally do take away the Principal Interface, and after that you could safely delete the Principal.storyboard and the ViewController.swift recordsdata, as a result of we’re not going to wish them in any respect.

Contained in the AppDelegate.swift file, it’s important to set the Principal module’s view controller as the foundation view controller, so it ought to look considerably like this:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder 

    var window: UIWindow?


extension AppDelegate: UIApplicationDelegate 

    func software(_ software: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 

        self.window = UIWindow(body: UIScreen.predominant.bounds)
        self.window?.rootViewController = MainModule().buildDefault()
        self.window?.makeKeyAndVisible()

        return true
    

Congratulations, you have created your very first VIPER module! 🎉


UITabBarController & VIPER

I’ve a brilliant easy resolution for utilizing a tab bar controller in a VIPER module. First let’s generate a number of new modules, these are going to be the tabs. I will use the JSONPlaceholder service, so lets say a separate tab for every of those assets: posts, albums, photographs, todos (with the identical module title). Generate all of them, and transfer them into the modules folder.

Now, let’s generate yet another module known as House. This can implement our tab bar controller view. In order for you you need to use the Principal module for this objective, however I prefer to maintain that for animation functions, to have a neat transition between the loading display and my House module (all of it is determined by your wants).

So the principle logic that we’ll implement is that this: the principle view will notify the presenter in regards to the viewDidAppear occasion, and the presenter will ask the router to show the House module. The House module’s view might be a subclass of a UITabBarController, it’s going to additionally notify it is presenter about viewDidLoad, and the presenter will ask for the correct tabs, by utilizing its router.

Right here is the code, with out the interfaces:

class MainDefaultView: UIViewController 

    var presenter: MainPresenter?

    override func viewDidAppear(_ animated: Bool) 
        tremendous.viewDidAppear(animated)

        self.presenter?.viewDidAppear()
    


extension MainDefaultPresenter: MainPresenter 

    func viewDidAppear() 
        self.router?.showHome()
    


extension MainDefaultRouter: MainRouter 

    func showHome() 
        let viewController = HomeModule().buildDefault()
        self.viewController?.current(viewController, animated: true, completion: nil)
    



extension HomeDefaultView: HomeView 

    func show(_ viewControllers: [UIViewController]) 
        self.viewControllers = viewControllers
    




extension HomeDefaultPresenter: HomePresenter 

    func setupViewControllers() 
        guard let controllers = self.router?.getViewControllers() else 
            return
        
        self.view?.show(controllers)
    



extension HomeDefaultRouter: HomeRouter 

    func getViewControllers() -> [UIViewController] 
        return [
            PostsModule().buildDefault(),
            AlbumsModule().buildDefault(),
            PhotosModule().buildDefault(),
            TodosModule().buildDefault(),
        ].map  UINavigationController(rootViewController: $0) 
    


class HomeModule 

    func buildDefault() -> UIViewController 
        

        presenter.setupViewControllers()

        return view
    

There may be one extra line contained in the House module builder perform that triggers the presenter to setup correct view controllers. That is simply because the UITabBarController viewDidLoad technique will get known as earlier than the init course of finishes. This behaviour is sort of undocumented however I assume it is an UIKit hack with the intention to keep the view references (or only a easy bug… is anybody from Apple right here?). 😊

Anyway, now you’ve got a correct tab bar contained in the venture built-in as a VIPER module. It is time to get some knowledge from the server and right here comes one other essential lesson: not all the pieces is a VIPER module.


Providers and entities

As you may seen there isn’t any such factor as an Entity inside my modules. I often wrap APIs, CoreData and lots of extra knowledge suppliers as a service. This fashion, all of the associated entities will be abstracted away, so the service will be simply changed (with a mock for instance) and all my interactors can use the service by way of the protocol definition with out realizing the underlying implementation.

One other factor is that I all the time use my promise library if I’ve to cope with async code. The explanation behind it’s fairly easy: it is far more elegant than utilizing callbacks and elective result parts. It’s best to be taught guarantees too. So right here is a few a part of my service implementation across the JSONPlaceholder API:

protocol Api 

    func posts() -> Promise<[Post]>
    func feedback(for submit: Put up) -> Promise<[Comment]>
    func albums() -> Promise<[Album]>
    func photographs(for album: Album) -> Promise<[Photo]>
    func todos() -> Promise<[Todo]>




struct Put up: Codable 

    let id: Int
    let title: String
    let physique: String




class JSONPlaceholderService 

    var baseUrl = URL(string: "https://jsonplaceholder.typicode.com/")!

    enum Error: LocalizedError 
        case invalidStatusCode
        case emptyData
    

    non-public func request<T>(path: String) -> Promise<T> the place T: Decodable 
        let promise = Promise<T>()
        let url = baseUrl.appendingPathComponent(path)
        print(url)
        URLSession.shared.dataTask(with: url)  knowledge, response, error in
            if let error = error 
                promise.reject(error)
                return
            
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else 
                promise.reject(Error.invalidStatusCode)
                return
            
            guard let knowledge = knowledge else 
                promise.reject(Error.emptyData)
                return
            
            do 
                let mannequin = strive JSONDecoder().decode(T.self, from: knowledge)
                promise.fulfill(mannequin)
            
            catch 
                promise.reject(error)
            
        .resume()
        return promise
    


extension JSONPlaceholderService: Api 

    func posts() -> Promise<[Post]> 
        return self.request(path: "posts")
    

    

Often I’ve a mock service implementation subsequent to this one, so I can simply take a look at out all the pieces I would like. How do I change between these providers? Properly, there’s a shared (singleton – do not hate me it is fully nice 🤪) App class that I take advantage of largely for styling purposes, however I additionally put the dependency injection (DI) associated code there too. This fashion I can cross round correct service objects for the VIPER modules.

class App 

    static let shared = App()

    non-public init() 

    

    var apiService: Api 
        return JSONPlaceholderService()
    




class PostsModule 

    func buildDefault() -> UIViewController 
        let view = PostsDefaultView()
        let interactor = PostsDefaultInteractor(apiService: App.shared.apiService)

        

        return view
    




class PostsDefaultInteractor 

    weak var presenter: PostsPresenter?

    var apiService: Api

    init(apiService: Api) 
        self.apiService = apiService
    


extension PostsDefaultInteractor: PostsInteractor 

    func posts() -> Promise<[Post]> 
        return self.apiService.posts()
    

You are able to do this in a 100 different methods, however I presently favor this method. This fashion interactors can instantly name the service with some further particulars, like filters, order, type, and so on. Mainly the service is only a excessive idea wrapper across the endpoint, and the interactor is creating the fine-tuned (higher) API for the presenter.


Making guarantees

Implementing the enterprise logic is the duty of the presenter. I all the time use guarantees so a fundamental presenter implementation that solely hundreds some content material asynchronously and shows the outcomes or the error (plus a loading indicator) is only a few traces lengthy. I am all the time attempting to implement the three basic UI stack elements (loading, knowledge, error) by utilizing the identical protocol naming conventions on the view. 😉

On the view aspect I am utilizing my good outdated assortment view logic, which considerably reduces the quantity of code I’ve to put in writing. You may go together with the standard manner, implementing a number of knowledge supply & delegate technique for a desk or assortment view just isn’t a lot code in any case. Right here is my view instance:

extension PostsDefaultPresenter: PostsPresenter 

    func viewDidLoad() 
        self.view?.displayLoading()
        self.interactor?.posts()
        .onSuccess(queue: .predominant)  posts  in
            self.view?.show(posts)
        
        .onFailure(queue: .predominant)  error in
            self.view?.show(error)
        
    




class PostsDefaultView: CollectionViewController 

    var presenter: PostsPresenter?

    init() 
        tremendous.init(nibName: nil, bundle: nil)

        self.title = "Posts"
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been carried out")
    

    override func viewDidLoad() 
        tremendous.viewDidLoad()

        self.presenter?.viewDidLoad()
    


extension PostsDefaultView: PostsView 

    func displayLoading() 
        print("loading...")
    

    func show(_ posts: [Post]) 
        let grid = Grid(columns: 1, margin: UIEdgeInsets(all: 8))

        self.supply = CollectionViewSource(grid: grid, sections: [
            CollectionViewSection(items: posts.map  PostViewModel($0) )
        ])
        self.collectionView.reloadData()
    

    func show(_ error: Error) 
        print(error.localizedDescription)
    

The cell and the ViewModel is outdoors the VIPER module, I are inclined to dedicate an App folder for the customized software particular views, extensions, view fashions, and so on.

class PostCell: CollectionViewCell 

    @IBOutlet weak var textLabel: UILabel!



class PostViewModel: CollectionViewViewModel<PostCell, Put up> 

    override func config(cell: PostCell, knowledge: Put up, indexPath: IndexPath, grid: Grid) 
        cell.textLabel.textual content = knowledge.title
    

    override func dimension(knowledge: Put up, indexPath: IndexPath, grid: Grid, view: UIView) -> CGSize 
        let width = grid.width(for: view, gadgets: grid.columns)
        return CGSize(width: width, peak: 64)
    

Nothing particular, if you would like to know extra about this assortment view structure, it’s best to learn my different tutorial about mastering collection views.


Module communication

One other essential lesson is to learn to talk between two VIPER modules. Usually I’m going with easy variables – and delegates if I’ve to ship again some type of data to the unique module – that I cross round contained in the construct strategies. I will present you a very easy instance for this too.

class PostsDefaultRouter 

    weak var presenter: PostsPresenter?
    weak var viewController: UIViewController?


extension PostsDefaultRouter: PostsRouter 

    func showComments(for submit: Put up) 
        let viewController = PostDetailsModule().buildDefault(with: submit, delegate: self)
        self.viewController?.present(viewController, sender: nil)
    


extension PostsDefaultRouter: PostDetailsModuleDelegate 

    func toggleBookmark(for submit: Put up) 
        self.presenter?.toggleBookmark(for: submit)
    





protocol PostDetailsModuleDelegate: class 
    func toggleBookmark(for submit: Put up)


class PostDetailsModule 

    func buildDefault(with submit: Put up, delegate: PostDetailsModuleDelegate? = nil) -> UIViewController 
        let view = PostDetailsDefaultView()
        let interactor = PostDetailsDefaultInteractor(apiService: App.shared.apiService,
                                                      bookmarkService: App.shared.bookmarkService)
        let presenter = PostDetailsDefaultPresenter(submit: submit)

        

        return view
    


class PostDetailsDefaultRouter 

    weak var presenter: PostDetailsPresenter?
    weak var viewController: UIViewController?
    weak var delegate: PostDetailsModuleDelegate?


extension PostDetailsDefaultRouter: PostDetailsRouter 

    func toggleBookmark(for submit: Put up) 
        self.delegate?.toggleBookmark(for: submit)
    



class PostDetailsDefaultPresenter 

    var router: PostDetailsRouter?
    var interactor: PostDetailsInteractor?
    weak var view: PostDetailsView?

    let submit: Put up

    init(submit: Put up) 
        self.submit = submit
    


extension PostDetailsDefaultPresenter: PostDetailsPresenter 

    func reload() 
        self.view?.setup(with: self.interactor!.bookmark(for: self.submit))

        
        self.interactor?.feedback(for: self.submit)
        .onSuccess(queue: .predominant)  feedback in
            self.view?.show(feedback)
        
        .onFailure(queue: .predominant)  error in
            
        
    

    func toggleBookmark() 
        self.router?.toggleBookmark(for: self.submit)
        self.view?.setup(with: self.interactor!.bookmark(for: self.submit))
    

Within the builder technique I can entry each part of the VIPER module so I can merely cross across the variable to the designated place (similar applies for the delegate parameter). I often set enter variables on the presenter and delegates on the router.

It is often a presenter who wants knowledge from the unique module, and I prefer to retailer the delegate on the router, as a result of if the navigation sample modifications I haven’t got to vary the presenter in any respect. That is only a private desire, however I like the best way it seems to be like in code. It is actually onerous to put in writing down these items in a single article, so I would suggest to obtain my completed sample code from github.


Abstract

As you’ll be able to see I am utilizing numerous design patterns on this VIPER structure tutorial. Some say that there isn’t any silver bullet, however I imagine that I’ve discovered a very wonderful methodology that I can activate my benefit to construct high quality apps in a short while.

Combining Guarantees, MVVM with assortment views on prime of a VIPER construction merely places each single piece into the proper place. Overengineered? Perhaps. For me it is well worth the overhead. What do you consider it? Be at liberty to message me by way of twitter. It’s also possible to subscribe to my month-to-month publication under.





Source link

You might also like

The abstract Vapor service factory design pattern

SwiftNIO tutorial – The echo server

Introducing – Vapor cheatsheet – The.Swift.Dev.

Share30Tweet19
learningcode_x1mckf

learningcode_x1mckf

Recommended For You

The abstract Vapor service factory design pattern

by learningcode_x1mckf
February 1, 2023
0
The abstract Vapor service factory design pattern

I've written a number of articles about manufacturing unit design patterns on my weblog and this time I might like to speak a couple of particular one, which...

Read more

SwiftNIO tutorial – The echo server

by learningcode_x1mckf
January 27, 2023
0
SwiftNIO tutorial – The echo server

Intoducing SwiftNIO In the event you used a excessive degree net framework, corresponding to Vapor, up to now, you would possibly had some interplay with occasion loops...

Read more

Introducing – Vapor cheatsheet – The.Swift.Dev.

by learningcode_x1mckf
January 23, 2023
0
Introducing – Vapor cheatsheet – The.Swift.Dev.

Out there on Gumroad Thanks for supporting my work by buying the cheatsheet. 🙏 Download now A whole Vapor framework reference for novices. greater than...

Read more

iCloud Programming Tutorial for iOS: An Introduction

by learningcode_x1mckf
January 18, 2023
0
iCloud Programming Tutorial for iOS: An Introduction

Editor’s observe: This week, we work with Ziad Tamim once more to provide you an introduction of iCloud programming. You’ll learn to save and retrieve knowledge from iCloud.On...

Read more

Easy multipart file upload for Swift

by learningcode_x1mckf
January 18, 2023
0
Easy multipart file upload for Swift

I imagine that you've got already heard in regards to the well-known multipart-data add method that everybody likes to add recordsdata and submit type knowledge, but when not,...

Read more
Next Post
The Third Age of JavaScript: An Update from Reactathon

The Third Age of JavaScript: An Update from Reactathon

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Related News

How to use Java’s conditional operator ?:

How to use Java’s conditional operator ?:

September 12, 2022
C++ Lambda Expressions Explained | Built In

C++ Lambda Expressions Explained | Built In

February 1, 2023
6 JavaScript Optimization Tips From Google

6 JavaScript Optimization Tips From Google

September 29, 2022

Browse by Category

  • C#
  • C++
  • Java
  • JavaScript
  • Python
  • Swift

RECENT POSTS

  • Java :Full Stack Developer – Western Cape saon_careerjunctionza_state
  • Pay What You Want for this Learn to Code JavaScript Certification Bundle
  • UPB Java Jam brings coffeehouse vibes to Taylor Down Under | Culture

CATEGORIES

  • C#
  • C++
  • Java
  • JavaScript
  • Python
  • Swift

© 2022 Copyright Learning Code

No Result
View All Result
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#

© 2022 Copyright Learning Code

Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?