GithubHelp home page GithubHelp logo

ferranabello / viperit Goto Github PK

View Code? Open in Web Editor NEW
509.0 22.0 66.0 7.33 MB

Viper Framework for iOS using Swift

License: MIT License

Swift 99.06% Ruby 0.94%
viper cocoapods carthage swift swift5 swift-5

viperit's Introduction

Viperit

Language Build Status Platform License Codecov codebeat badge CocoaPods Carthage Compatible Accio Swift Package Manager compatible SwiftUI compatible

Write an iOS app following VIPER architecture. But in an easy way.

Viper the easy way

We all know Viper is cool. But we also know that it's hard to setup. This library intends to simplify all that boilerplate process. If you don't know yet what Viper is, check this out: Architecting iOS Apps with VIPER (objc.io)

Installation

Requirements

  • iOS 12.0+
  • Swift 5.1+
  • Xcode 12.0+

Swift Package Manager (SPM)

You can easily install this framework using SPM on Xcode. Go to File | Swift Packages | Add Package Dependency... in Xcode and search for "http://github.com/ferranabello/Viperit"

CocoaPods

CocoaPods is a dependency manager for Cocoa projects.

Specify Viperit into your project's Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!

pod 'Viperit'

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

To integrate Viperit into your Xcode project using Carthage, specify it in your Cartfile:

github "ferranabello/Viperit"

Run carthage update to build the framework and drag the built Viperit.framework into your Xcode project.

Features

Create modules easily using Xcode templates

Viperit Xcode templates can be downloaded from the latest release page. Download the Templates.zip file.

To install them, unzip the file, open your terminal and run:

cd PATH/TO/UNZIPPED/FOLDER
mkdir -p ~/Library/Developer/Xcode/Templates/
cp -R Viperit ~/Library/Developer/Xcode/Templates/

Module Creation

You can check "Also create a Storyboard file for module" if you want the storyboard file to be automatically created for you. Choose between "Universal" to use just one view for phones and tablets, and "Dedicated Tablet View" if you want to have a separated view for tablet devices.

Use storyboard, xib, programmatic views or SwiftUI

Any Viperit module will assume its view is loaded from a Storyboard by default. But you can use storyboards, nibs, code or even SwiftUI views! All you need to do is to override the variable viewType in your modules enum:

enum MySuperCoolModules: String, ViperitModule {
    case theStoryboardThing  
    case oldSchool
    case xibModule
    case whatTheSwift

    var viewType: ViperitViewType {
        switch self {
        case .theStoryboardThing: return .storyboard
        case .oldSchool: return .code
        case .xibModule: return .nib
        case .whatTheSwift: return .swiftUI
        }
    }
}

Built-in router functions

This framework is very flexible, it's meant to be used in any way you want, but it has some useful built-in functionalities in the router that you could use:

    //Show your module as the root view controller of the given window
    func show(inWindow window: UIWindow?, embedInNavController: Bool, setupData: Any?, makeKeyAndVisible: Bool)

    //Show your module from any given view controller
    func show(from: UIViewController, embedInNavController: Bool, setupData: Any?)

    //Show your module inside another view
    func show(from containerView: UIViewController, insideView targetView: UIView, setupData: Any?)

    //Present your module modally
    func present(from: UIViewController, embedInNavController: Bool, presentationStyle: UIModalPresentationStyle, transitionStyle: UIModalTransitionStyle, setupData: Any?, completion: (() -> Void)?)

Easy to test

You can test your module injecting mock layers like so:

    var module = AppModules.home.build()
    view = HomeMockView()
    view.expectation = expectation(description: "Test expectation")
    module.injectMock(view: view)
    ...

Usage

Now, let's create our first Viperit module called "myFirstModule"!

0. Create your module files

Let's use the provided Xcode template to easily create all the needed classes for the module. Just click New file in the document panel and choose between Protocol-oriented module, Object-oriented module or SwiftUI module under the "Viperit" section.

1. Create a modules enum

You need at least one (you can use as many as you like, maybe to group modules by functionality) enum that implements the ViperitModule protocol to enumerate your application modules.

import Viperit

//MARK: - Application modules
enum AppModules: String, ViperitModule {
    case myFirstModule
}

2. Build the module and perform navigation

Imagine this is a new app and we want to load our "myFirstModule" module as the app's startup module

import Viperit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        let module = AppModules.myFirstModule.build()
        let router = module.router as! MyFirstModuleRouter
        router.show(inWindow: window)
        return true
    }
}

This is just an example, you could of course use your own router functions instead of the provided show(inWindow:):

    window = UIWindow(frame: UIScreen.main.bounds)
    let module = AppModules.myFirstModule.build()
    let router = module.router as! MyFirstModuleRouter
    router.mySuperCoolShowFunction(inWindow: window)

2.1. Are you using SwiftUI?

Let's say you created a module based on SwiftUI called 'Cool'. All you need to do is to use the new Viperit SwiftUI module builder:

import SwiftUI
import Viperit

//A sample function that could be implemented in some Router to show your Cool SwiftUI module
//You can even inject an @EnvironmentObject view model to your SwiftUI view.
func showTheCoolModule() {
  let module = AppModules.cool.build { presenter -> (CoolView, UserSettings) in
      let p = presenter as! CoolPresenterApi
      let settings = p.settings()
      return (CoolView(presenter: p), settings)
  }
  let router = module.router as! CoolRouter
  router.show(from: viewController)
}

Check the example app to have a better understanding of how it works, and how to manage data flow on SwiftUI with the presenter.

3. Follow the Viper flow

Everything is ready for you to make great things the Viper way! Clone the repo and run the 'Example' target to see it in action! Or just try it with Cocoapods:

pod try Viperit

Author

Ferran Abelló

License

Viperit is released under MIT license and copyrighted by Ferran Abelló.

viperit's People

Contributors

ferranabello avatar moux2003 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

viperit's Issues

Two questions

Hi,

interesting framework and library, I've two questions:

  • If I need a custom UIViewController what do I have to do? I've to create a custom UserInterface that inherits from my custom UIViewController? I've seen that UserInterface inherits from UIViewController. Here (#69) you say that we have to thins Eureka as a subview but it complicates things.
    If we want to simplify could we create a custom UserInterface. Could you give us an example?

  • In you framework there's DisplayData. How should it be used?

Thanks

final classes unavailable for Mocks

Hi, i really like your framework and template for Xcode.
But classes from template are final and unavailable for inherence and Mocking in Unit Tasts.
Can you add new Template or change it to not final classes for layers Router, Interactor, Presenter? Or if you want i can create pull request to you.

P.S. Maybe add optional check box to module creation window (like "Layers are final")?

Advanced navigation

@ferranabello
Any hints on advanced navigation?

Routing examples:

let's start LoggedOutModule without navigation bar
we've got 2 paths:

  • register - with navigationBar
  • login - with navigationBar

Register process got 3 steps:

  • registerFormModule - with navigationBar
  • terms&conditionsModule - with navigationBar
  • registerSuccessModule - without navigation bar

After register process we should unwind/jump back/show LoggedOutModule once again (without navigationBar).

How to handle whole navigation bar embedding/showing/hiding process + backrouting to previous modules (without navigation bars e.x).

My current idea is for showing-hiding of navigation controller is

    override func viewIsAboutToAppear() {
        super.viewIsAboutToAppear()
        _view.navigationController?.setNavigationBarHidden(true, animated: false)
    }

And for routback to view is to just dismiss view which is currently presented over LoggedOutView

// MARK: - RegisterSuccessRouter API
extension RegisterSuccessRouter: RegisterSuccessRouterApi {
    func routeToLoggedOutModule() {
        _view.dismiss(animated: true, completion: nil)
    }
}

Is this correct approach?

AppModule.build() crashes app

I've set optimization level "optimize for speed".

        let module = module.build()        
        module.router.show(from: viewController, embedInNavController: true, setupData: id)

embedInNavController: true seems to be crucial here.

I am calling router.show() with already existing navigation controller to archive card like presentation style with different navigation context.

In that situation app crashes because of nil _preseter. With optimization for speed turned off everything seems to be fine.

Any ideas how to handle? Currently I've turned off optimization.

Get-only viper components do not allow protocol properties

Scenario

Trying to mock a splash page that will wait for resources to be loaded before push to home I have this code:

// MARK: - Public Interface Protocol
protocol SplashViewInterface {
    var isStuffReady: Bool { get set }
}
//....
extension SplashPresenter: SplashPresenterInterface {
    func stuffReady() {
        view?.isStuffReady = true //Error: Cannot assign to property: 'view' is a get-only property
    }
}

To solve this I have 2 options:

Auto generated setters for Viper components

// MARK: - VIPER COMPONENTS API (Auto-generated code)
private extension SplashPresenter {
    var view: SplashViewInterface? {
        get {
            return proxyView as? SplashViewInterface
        }
        set {
            proxyView = newValue as? UserInterface
        }
    }

Avoid properties on modules interfaces

Instead use a implicit computed property

protocol SplashViewInterface {
    var isStuffReady: Bool { get set }
    func setStuffLoaded(isLoaded: Bool)
}
//....
/ MARK: Splash View
final class SplashView: UserInterface {
    var isStuffReady = false
}

// MARK: - Public interface
extension SplashView: SplashViewInterface {
    func setStuffLoaded(isLoaded: Bool) {
        isStuffReady = isLoaded
    }
}

Question

Does it make sense to have properties on protocols?

Modules integration

Hi!
First of all - thank you for great framework!
What is the best practice of integration of one module into another?
E.g: ParentModule contains table, and the ChildModule contains view for ParentModule tableHeaderView. What is the proper way to integrate ChildModule into ParentModule?
Thank you!

Provider injection

I have a question: how do you plan to inject provider layer with module? ThanX

App crashes run time

I have build a project according to viperit instructions then app crashes on the start .That can be seen in screen shot .Here is my AppDelegate source
` import UIKit
import Viperit
enum AppModules: String, ViperitModule {
case myFirstModule
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    window = UIWindow(frame: UIScreen.main.bounds)
    let module = AppModules.myFirstModule.build()
    module.router.show(inWindow: window)
    return true
}

`
screen shot 2017-09-28 at 11 14 56 am

Viperit: Unexpectedly found nil while implicitly unwrapping an Optional value

There's an issue that happens only when downloading the app from testflight, otherwise running from Xcode works fine and I don't use storyboard or xib files it's only code

Screen Shot 2021-07-11 at 11 34 37 AM

And here's the implementation

window = UIWindow(windowScene: scene)
let module = AppModules.splash.build()
module.router.show(inWindow: window, embedInNavController: false, setupData: nil, makeKeyAndVisible: true)

RxBindings between presenter and view

@ferranabello
What do you think about exposing

DisplayData to presenter to use it as exchange object for RxBindings between View and Presenter?

If it's not an option what is your suggestion to handle this kind of bindings?

Setup data on Interactor.

As i understand VIPER, the interaction is the only one data provider to the presenter, so the setup data should be injected onto it and retrieved by the presenter when showing the module.

Along with this and for not create another issue, I didn't´t get the key concept of DisplayData entity and on my work I'm omitting it.

How to test modules?

Hello. Thanks for very useful project.
And how we can test module (presenter, interactor)? We need mock some layers (view for example) but we can't.

Compile error in pod on Xcode 10 beta 5

In Router class:

open func show(from: UIViewController, embedInNavController: Bool = false, setupData: Any? = nil) {
        process(setupData: setupData)
        let view = embedInNavController ? embedInNavigationController() : _view
        from.show(view, sender: nil)
    }

Line 38 from.show(view, sender: nil)has error: Value of optional type 'UIViewController?' must be unwrapped to a value of type 'UIViewController'

Anyone else seeing this?

Alamofire API Request

Could I get the sample format with Alamofire in Viperit like iOS-Viper-Architecture. And How do I create the storyboard for iPad and iPhone within one module because iPad and iPhone was difference Design? Thanks a lot.

ViewController presentation styles

Hi,

First of all - good job. It is sth I was looking for recently.

Few questions.
Is it possible to request for implementation of > UIModalPresentationStyle

What is your suggestion about SplitViewControllers in that case? Skip the router part?

Suggestion: Event about Module Builded

What do you think if we add event for each layer about Module Builded
For example:
Each layer implement protocol ViperLayer
with func onModuleBuilded event
we call this after all layers is connected in build

this helps us with initalization for each layer when dependecies are connceted
For example:
in this event we can call presenter from interactor after init

P.S. Now if we override standard init we can't call presenter from interactor

Example update

@ferranabello
Hey,

Any chance for updated example?

I wonder how you imagine for example ApiProtocol part.

Let's assume that we've got LoggedOut module.
Let's click button login

Protocol supposed to look like

protocol LoggedOutRouterApi: RouterProtocol {
    func showRegisterModule()
}
//MARK: - LoggedOutPresenter API
protocol LoggedOutPresenterApi: PresenterProtocol {
    func showRegisterModule()
}

declaring those methods twice in protocol and

final class LoggedOutView: UserInterface {
    @IBAction func presentRegister() {
        presenter.showRegisterModule()
    }
}

as action handling for view?

There are lot more things I am not sure how to use with Viperit :) It would be nice to have this example updated.

How to pass data between Modules?

Suppose form ModuleA to ModuleB I have to send a object.

// form moduleA router
let moduleB = AppModules.ModuleB.build()
module.router.show(from: _view)

Support UITableViewController

Any plans for supporting the lib to use UITableViewControllers instead of only using UIViewController (UserInterface class implementation)?

Dependency Injection (Swinject)

Hi @ferranabello
I'm going to start new project with Viperit and Swinject for DI.
What about DI usage in Viperit? For example i want to inject Services and Providers to Interactor. How i can do that? I think good idea is send DI Container to .build function. What do you think?

One view, multiple modules

Is it any neat way to use one view (storyboard/xib/whatever) with multiple modules?
Example:
We've got same, template style overlay view. Let's call it ActionOverlayView.
It has always exactly same construction: header, description, button1, button2 etc. but totally different behaviour.
Once it will redirect to different parts of app, other time it's going to resend verification email, logout user etc.
There is totally no point of copy pasting whole view only for the same of module.

That is why I think it might be useful to move viewIdentifier to ViperItModuleProtocol and allow us to change default viewIdentifier.

VipertitModuleProtocol
var viewIdentifier: String? { get }

Module
let viewIdentifier = module.viewIdentifier ?? safeString(NSStringFromClass(viewClass).components(separatedBy: ".").last)

In that case we might be able to use one view with multi-purpose modules.

Swift 3 syntax error

Starting a project from scratch Xcode and added my fork of this repo as a development pod Xcode complains about Swift 3 syntax, I perform the automatic conversion and raises about 94 errors, what I'm doing wrong?

Modally presentation styles

Hello!

I use your library and I like it.
I need to use a special modal presentation style but it's difficult. I want to propose adding this functionality to your cool library ;)

Build storyboard issue

Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/chris/Nutstore Files/XCode/VIPER/ViperItExample/Pods/Viperit/Viperit/Core/Module.swift, line 28
2020-06-13 12:05:39.324354+0800 ViperItExample[2698:112714] Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/chris/Nutstore Files/XCode/VIPER/ViperItExample/Pods/Viperit/Viperit/Core/Module.swift, line 28

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow windowto the provided UIWindowScenescene. // If using a storyboard, the windowproperty will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (seeapplication:configurationForConnectingSceneSession` instead).

// guard let _ = (scene as? UIWindowScene) else { return }

    guard let scene = (scene as? UIWindowScene) else { return }

    window = UIWindow(frame: UIScreen.main.bounds)
    window?.windowScene = scene

    let module = AppModules.ceshi.build()
    let router = module.router as! CeShiRouter
    module.router.show(inWindow: window, embedInNavController: true, setupData: nil, makeKeyAndVisible: true)
    
}`

ViperIt unable to compile with Xcode 13

class HostingUserInterface - file UserInterface+SwiftUI.swift line 16
Incorrect argument labels in call (have 'nibName:bundle:', expected 'coder:rootView:')

Apply SwiftLint

First of all let me congratulate you, this is the best approach to VIPER I have seen and it's close to mine but using a bigger knowledge on Swift.

But it doesn't compile against https://github.com/realm/SwiftLint , i can exclude your Pod from the listing process but as i rather to understand your source would be cooler if it were linted.

Even excluding Pods from the linting, the modules generated by the template includes at least two lint errors that could be easily fixed:

  • the forced casts on VIPER componentes getters var presenter: LoginPresenter { return _presenter as! LoginPresenter }
  • the use of underscore on the same VIPER componentes, on my VIPER architecture I called them proxyPresenter, proxyRouter...

How do I import the other library?

I installed Viperit and Hero Elegant Transition Library with Pod. But If I would like to use Hero Transition, I need to import Hero Library in Module.swift from Viperit. How do I import the Hero framework with cocoapod? Could I please get another solution? Thanks a lot.

injection module

Hi,
How to use dependency injection in module ?
Do you add template dependency injection.

SwiftUI weak linking

Do you think it is possible to add availability for SwiftUI features in framework?
I accidentally run into crash because of that :(
Sth like
#if isAvailable()
or version check might be helpful :)

My failure was

dyld: Library not loaded: /System/Library/Frameworks/SwiftUI.framework/SwiftUI
Referenced from: /Users/piotrgawlowski/Library/Developer/CoreSimulator/Devices/818027B6-FE01-489E-937A-E7764561A2CE/data/Containers/Bundle/Application/D9182AB1-13FE-40CE-A395-45E6B06EA2F4/eForskrivning.app/Frameworks/Viperit.framework/Viperit
Reason: image not found

How to integrate the 'Eureka' framework

First of all, thank you very much for the Viper framework you provide. It's really very simple to use.
Now I have a problem. I want to integrate the 'Eureka' framework. This framework can easy to build form.
But needs to inherit the 'FormViewController'.

In Viper framework, the 'UserInterface' class is inherit 'UIViewController'.
So, could you help me?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.