VIPER Modules

All about VIPER

VIPER is not a framework but an approach to iOS application architecture, which stands for:

VIEW

The view consists of views and view controllers. It is responsible to receive user interactions and pass them to presenters for decision making. To keep the view simple, it shouldn’t contain any view logics. This is where to define how the view looks like, and nothing beyond this.

PRESENTER

The presenter defines the view logics, e.g. when to show a warning message or highlight a button. It is responsible to prepare content for the view to display. Whenever data is required, the presenter requests data from interactors (but not directly from the model).

INTERACTOR

The interactor mainly contains business logic, e.g. logging in the user / processing a purchase / sending a friend request. It should be independent of the UI. It only handles requests from the presenter and prepare corresponding data regardless of how the view looks like.

ENTITY

Entities are the model objects manipulated by an Interactor and only by the Interactor. It is simply a Struct. It is model orientated and therefore should not contain any business logic, Entity is not the Model, it's a represntation of the Model.

ROUTING (WIREFRAME)

Wireframe defines the routes from one screen to another. In VIPER, the responsibility for Routing is shared between the presenter and the wireframe. When the presenter receives user interactions and decided to navigate to another screen, it will use the wireframe to perform the desired navigation (to which screen and how to navigate). The wireframe can also contain transition animations.


Why VIPER?



After using VIPER, I've found it to be very beneficial in many ways. Let’s get back to the list of things we set out to accomplish when architecting our app to see if VIPER addresses them.
  • Single responsibility principles applied.
  • Easy to iterate on.
  • Collaboration friendly.
  • Separated out concerns.
  • Spec-ability.
  • Testability.

0

Back To Top

VIPER Module Structure

The BarCloud app is based on a Swift 4.2 VIPER Module Generator with predefined functions and a BaseProtocol.

Are you new to VIPER Design Pattern? Want to Learn it But feel overwhelmed of all the files you have to create?

When you decide to use VIPER architecture in your project, it feels overwhelming to create new modules, because you need create at least 6 files for each. Imagine that you need to create 6 modules...We were suffering of this problem, and this is the reason why We've created this template. It's very useful for us and I hope that it be useful for you too.

Available Generator options:

  • BaseProtocol - A Common Functions Placeholder.
  • With Generic Functions & Short-name - Full name filenames - check Table below for short-names.
  • With Generic Functions - Protocols with Generic Functions ready for implementation.
  • Without Generic Functions.
  • Through terminal - see the Generama Viper section.

The Full name dropdown will Generate full filenames with Generic Functions.

 

Other Dropdown Selections With OR Without Generic Functions.

File Name

Acts As

Description

{YourFileName}ViewController.swift

UIViewController

This is where your design layout should be.

{YourFileName}ViewController.xib

UI - XIB

A file where you can add any UI elements.

{YourFileName}DefaultBuilder.swift

Builder

If you use Storyboard, Link this to it and ignore UI file.

{YourFileName}InteractorImp.swift

Interactor

This is what will communicate with the Data Layer(Services) and Entities(Defined in BarCloudSDK).

{YourFileName}PresenterImp.swift

Presenter

This is what will communicate with the Router, View and the Interactor.

{YourFileName}ViewModel.swift

ViewModel

This is the ViewModel,  Presenter should prepare the view models from entities, calling the services or managers from BarCloudSDK. ViewModels will be displayed on the UIViewController.

{YourFileName}Router.swift

Router

This is where your routes to another views should be.

 

BaseProtocol Option:

File Name

Acts As

Description

{YourFileName}View.swift

UIView

Declare here the methods, to call them in presenter.

{YourFileName}Presenter.swift

Presenter

Declare here the methods, to call them in view.

{YourFileName}Interactor.swift

Interactor

Declare here the methods, to call them in presenter.

{YourFileName}Router.swift

Router

Declare here the methods, to call them in presenter.

 

Module Files Structure:

├── Builder(Assembly)
│   └── YOUR_MODULE_Builder.swift # Inject your dependencies heare
├── Interactor
│   ├── YOUR_MODULE_NAMEInteractor.swift # Add your business logic here
├── Presenter
│   └── YOUR_MODULE_NAMEPresenter.swift # This is what will communicate with the Router, View and the Interactor.
├── Router
│   ├── YOUR_MODULE_NAMERouter.swift # Play with UIViewControllers
├── View
    ├── YOUR_MODULE_NAMEView.swift # This is where your design layout should be.
    ├── ViewModel
        └── YOUR_MODULE_NAMEViewModel.swift # This is what will display on the View.
 

Here's how it looks like:

0

Back To Top

Generamba

Generamba is a code generator made for working with Xcode. Primarily it is designed to generate VIPER modules but it is quite easy to customize it for generation of any other classes (both in Objective-C and Swift).

Module generation

Terminal Command: generamba gen [MODULE_NAME] viper-swift Description: Use this command to create new modules from the specified template. Note that you should run it in the same directory where Rambafile is located. Required parameters:
  • MODULE_NAME - this is your code module name. It's used in a lot of different places - in subfolders, Xcode groups, filenames, and in your classes names.
  • TEMPLATE_NAME - this is a name of the template you want to use for creating new module.

0

Back To Top

Rambafile

In the root project folder, you can find a Rambafile.
### Headers settings
company: Mister Grizzly Inc.

### Xcode project settings
project_name: Project
xcodeproj_path: Project.xcodeproj

### Code generation settings section
# The main project target name
project_target: Project

# The file path for new modules
project_file_path: Project/Modules

# The Xcode group path to new modules
project_group_path: Project/Modules

### Tests generation settings section
# The tests target name
test_target: ProjectTests

# The file path for new tests
test_file_path: ProjectTests/Modules

# The Xcode group path to new tests
test_group_path: ProjectTests/Modules

### Dependencies settings section
podfile_path: Podfile

### Templates
templates:
- {name: viper_swift}
 

0

Back To Top

Services Application Builder

BarCloudSDK uses 2 services. LoginService and ItemService. Both are injected in ApplicationServiceBuilder.swift. Please check the project file.
import Swinject
import Foundation

/**
 This is an Application Service Builder class.
 */
public final class ApplicationServiceBuilder {
  
  fileprivate struct Static {
    static var sharedContainer: Container?
  }
  
  /**
   This is a singleton container.
   Some services should be shared across application
   ### Usage Example to register a service: ###
   After: Static.sharedContainer = Container { container in
   
   add:
   
   container.register(YourServiceName.self) { r in
   return YourServiceNameDefault()
   }
   ````
   container.register(NetworkClient.self) { r in
   return NetworkClientDefault()
   }
   
   ````
   */
  public static var defaultContainer: Container {
    Static.sharedContainer = Container { container in
      
      container.register(KeyChainManager.self) { _ in
        return KeyChainManagerDefault()
      }
    }
    
    return Static.sharedContainer!
  }
}
Once you've decided to use the BarCloud SDK services, you'll need to change the Container from {ModuleName}DefaultBuider to:
import Swinject
import BarCloudSDK

class ModuleNameDefaultBuilder: SplashBuilder {   

   let container = Container(parent: ApplicationServiceBuilder.defaultContainer)

   /* ... */
}

0

Back To Top

App Modules

SignUp

We have injected here the login service:
SignUpDefaultBuilder {...
  let loginService = container.resolve(LoginService.self, name: String(describing: LoginServiceDefault.self))!

}
In the interactor we calling the LoginService's signUp method, which returns in a completion  a boolean value - true or false if the user was successfully logged in or not, and an error if will be.
func signUp(with credentials: Credentials,
             completion: @escaping (Bool, Error?) -> Void) {
   loginService.signUp(with: credentials, completion: completion)
 }
The textfields dependency is called TweeTextField, take a look into Development Pods. From View Controller, in the UI method, we have configured the textfields:
nameTextField.prepareTextField(with: "user_your_name".localized.uppercased()) // you can change with your own in localized files.
nameTextField.tag = TextFieldType.name.rawValue // A tag for textfield in int format
nameTextField.showInfo(animated: true)
nameTextField.placeholder = "user_your_name_placeholder".localized // you can change with your own in localized files.
nameTextField.addTarget(self, action: #selector(textFieldDidChange(_:)),
                            for: .editingChanged)

0

Back To Top

QA

Questions and Answer module is displaying the view model structure:
struct QAViewModel {
  let question: String
  let answer: String
  
  init(question: String,
       answer: String) {
    
    self.question = question
    self.answer = answer
  }
}
Where the cell is expanded and it shows the answer. Both are received from the backend where admin has an option to add, delete or edit.

0

Back To Top

SignIn

Everything is happening like in SignUp module. Please take a look. We have here 2 textfields only - email and password.

0

Back To Top

Passcode

A Swift implementation of passcode lock for iOS with TouchID authentication.

Usage

  1. Create an implementation of the PasscodeRepositoryType protocol.
  2. Create an implementation of the PasscodeLockConfigurationType protocol and set your preferred passcode lock configuration options. If you set the maximumInccorectPasscodeAttempts to a number greather than zero when the user reaches that number of incorrect passcode attempts a notification with name PasscodeLockIncorrectPasscodeNotification will be posted on the default NSNotificationCenter.
  3. Create an instance of the PasscodeLockPresenter class. Next inside your UIApplicationDelegate implementation call it to present the passcode in didFinishLaunchingWithOptions and applicationDidEnterBackground methods. The passcode lock will be presented only if your user has set a passcode.
  4. Allow your users to set a passcode by presenting the PasscodeLockViewController in .setPasscode state:
let configuration = ... // your implementation of the PasscodeLockConfigurationType protocol

let passcodeVC = PasscodeLockViewController(state: .SetPasscode, configuration: configuration)

presentViewController(passcodeVC, animated: true, completion: nil)
You can present the PasscodeLockViewController in one of the four initial states using the LockState enumeration options: .enterPasscode, .setPasscode, .changePasscode, .removePasscode. Also you can set the initial passcode lock state to your own implementation of the PasscodeLockStateType protocol.
internal func application(_ application: UIApplication,
                          didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  // Override point for customization after application launch.
  
  passcodeLockPresenter.presentPasscodeLock()

  return true
}

internal func applicationDidEnterBackground(_ application: UIApplication) {
  passcodeLockPresenter.presentPasscodeLock()
}

Customization

Custom Design

The PasscodeLock will look for PasscodeLockView.xib inside your app bundle and if it can't find it will load its default one, so if you want to have a custom design create a new xib with the name PasscodeLockView and set its owner to an instance of PasscodeLockViewController class and module to PasscodeLock. Then connect the view outlet to the view of your xib file and make sure to connect the remaining IBOutlets and IBActions. Also make sure to set module to PasscodeLock on all PasscodeSignPlaceholderView and PasscodeSignButton in the nib. PasscodeLock comes with two view components: PasscodeSignPlaceholderView and PasscodeSignButton that you can use to create your own custom designs. Both classes are @IBDesignable and @IBInspectable, so you can see their appearance and change their properties right inside the interface builder:

Localization

Take a look at PasscodeLock/en.lproj/PasscodeLock.strings for the localization keys. Here again the PasscodeLock will look for the PasscodeLock.strings file inside your app bundle and if it can't find it will use the default localization file.

0

Back To Top

ResetPassword

Everything is happening like in SignUp module. Please take a look. We have here 1 textfield only - email.

0

Back To Top

Salient Templates

Hi, My name is Garfield.

How are you?

0

Back To Top

Home

HomeController is inherited from ScannerViewController. We have these settings:
override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated)
   pulleyViewController?.displayMode = .automatic
   prepareScanner()
 }

 override func viewWillDisappear(_ animated: Bool) {
   super.viewWillDisappear(animated)
   stopCapturing()
 }
 
 private func setupUI() {
   view.bringSubviewToFront(menuButton)
   view.bringSubviewToFront(flashButton)
   //StoreReview.request(from: self, afterAppLaunches: 15) // Uncomment if you want to request from user a store review. Change also number of app launches.
 }
prepare Scanner:
private func prepareScanner() {
  setupScanner(nil,
               UIColor.white.withAlphaComponent(0.4), // Change to your 
               .default, "camera_centered_text".localized, // Change to your
               "Scanned", "wav", flashButton) { [weak self] code in
                guard let self = self else { return }
                self.presenter.openSearchAction()
  }
}
The flash button is enabled when the brightness is less than -1.0. From Presenter we delegate the action from menu controller:
internal func didDismiss(_ controller: MenuViewController?,
                         for type: MenuItemType) {    
  switch type {
  case .contactUs:
  case .items:
  case .tutorial:
  case .logOut:
  case .settings:
  case .qa:
  }
}
For every case we have added the needed action. If you want to change one of them, be free.

0

Back To Top

Menu

Menu items:
var menuItems: [String: MenuItemType] {
   return ["items_title".localized: MenuItemType.items,
           "settings_title".localized: MenuItemType.settings,
           "qa_title".localized: MenuItemType.qa,
           "tutorial_onboarding".localized: MenuItemType.tutorial,
           "contact_us_title".localized: MenuItemType.contactUs,
           "log_out_title".localized: MenuItemType.logOut]
 }
If you want to rename one of them, go to localized files, find the key and change inside the value.  

0

Back To Top

Splash

Splash is registered in AppDelegate.swift:
internal func application(_ application: UIApplication,
                          didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  // Override point for customization after application launch.
  
  window = UIWindow(frame: UIScreen.main.bounds)
  let splash = SplashDefaultBuilder().buildSplashViewController()!
  let navigationController = UINavigationController(rootViewController: splash)
  navigationController.isNavigationBarHidden = true
  window?.rootViewController = navigationController
  window?.makeKeyAndVisible()

  return true
}
SplashBuilder has registered the UserPreferencesManager. Router has 3 methods:
...ToHome() -> navigate to app
...Onboarding() navigate to tutorial
...ToSelectOption() and navigate to select option - SignIn or SignUp
Splash Interactor's has two properties:
is...LoggedIn
is...AppLaunch
What's happening in Presenter? Only if the app is run first time, we will redirect the user to Tutorial screen. Onboarding. We have to check if user is logged in, if yes - we will redirect him to home, if not, to Screen selection.
func present()
Last file - ViewController.

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  presenter.present()
}
We call the presenter.present() method in view did appear.

0

Back To Top

Items

A simple present method here:
func present() {  
  interactor.getItems { [weak self] (items, error) in
    guard let self = self else { return }
    
    if let items = items {
      for (index, item) in items.enumerated() {
        
        let viewModel = ItemsViewModel()
        viewModel.index = index
        viewModel.name = item.productName
        viewModel.date = item.createdAt
        viewModel.categoryName = item.item
        viewModel.images = item.images.compactMap { $0.img }
        viewModel.imagePreview = item.imagePreview
        viewModel.id = "\(item.id)"
        
        self.viewModels.append(viewModel)
      }
  
    } else {
      if let error = error {
        self.view.showAlert(error: error, handler: nil)
      }
    }
  }
}
Don't forget to enable or disable actions, just change the inside action method or comment the view model property.

For cell swipes we use the SwipeCellKit dependency.

For quick look of exported files, we use the QuickLook framework, and present a QLPreviewController.
func showQuickLookPreview(with url: URL) {    
   let controller = QLPreviewController()
   controller.dataSource = self
   viewController.present(controller, animated: true)
 }


Tableview settings delegate:
internal func tableView(_ tableView: UITableView,
                        heightForRowAt indexPath: IndexPath) -> CGFloat {
  return dataSource?.viewModels[indexPath.row].rowHeight ?? UITableView.automaticDimension
}

internal func tableView(_ tableView: UITableView,
                        heightForHeaderInSection section: Int) -> CGFloat {
  return 30.0
}

internal func tableView(_ tableView: UITableView,
                        viewForHeaderInSection section: Int) -> UIView? {
  let headerView = UIView()
  headerView.backgroundColor = .white
  
  return headerView
}


Sorting by date and name:

private func sortByDate() {
  func sortArray(by first: ItemsViewModel, second: ItemsViewModel) -> Bool {
    if let firstDate = first.getDate(from: first.date),
      let secondDate = second.getDate(from: second.date) {
      return firstDate.compare(secondDate) == .orderedAscending
    }
    
    return false
  }
  
  let sortedViewModels = viewModels.sorted(by: { sortArray(by: $0, second: $1) })
  view.prepare(with: sortedViewModels)
}

private func sortByName() {
  func sortArray(by first: ItemsViewModel, second: ItemsViewModel) -> Bool {
    if let firstName = first.name,
      let secondDName = second.name {
      return firstName < secondDName
    }
    
    return false
  }
  
  let sortedViewModels = viewModels.sorted(by: { sortArray(by: $0, second: $1) })
  view.prepare(with: sortedViewModels)
}
 

We have injected here the user preferences manager and item services to operate with items.
let itemService = container.resolve(ItemService.self)!
      let userPreferencesManager = container.resolve(UserPreferencesManager.self)!
      
      return ItemsInteractorImp(with: itemService,
                                userPreferencesManager: userPreferencesManager)
 

0

Back To Top

SelectOption

In the ViewController we have two actions:
@IBAction private func signInTapped() {
  presenter.onSignInTapped()
}

@IBAction private func signUpTapped() {
  presenter.onSignUpTapped()
}
We have to push them to navigation from router:
func navigateToSignIn() {
  let controller = SignInDefaultBuilder().buildSignInViewController()!
  viewController.navigationController?.pushViewController(controller, animated: true)
}

func navigateToSignUp() {
  let controller = SignUpDefaultBuilder().buildSignUpViewController()!
  viewController.navigationController?.pushViewController(controller, animated: true)
}
UI:
navigationController?.navigationBar.isHidden = true
addCustomBackImage()
addCustomColorNavigationBarTitle(.mainColor)
createAccountButton.titleLabel?.font = UIFont.SFProSemibold(17)
If you'll decide to change the createAccountButton option, we will cover this part on reskin section.

0

Back To Top

Search

Search controller is presented like in iOS maps. For that we use the Pulley dependency. We added on Developer dependencies, because we added some custom work with hiding and presenting.
func hideTableView() {
  tableView.isHidden = true
  closeButton.isHidden = true
  topImageView.isHidden = true
  titleLabel.isHidden = true
}
We have configured those options when the barcode is detected, we will show the search text and icon, after then we sending an request with barcode, and the received items will be displayed on search tableview cell. The height of search controller will changed to Screen size/2, and user will have options to play with it till the top or hide it tap on cancel button.

0

Back To Top

Onboarding

Onboarding view Model is builded like that:
class OnboardingViewModel {
  var imageName: String?
  var description: String?
  var titleAttributedString: NSMutableAttributedString?
}

let slide5 = OnboardingViewModel()
slide5.imageName = "onboarding5" // Check the Assets.xcassets file, you can remove image and change with your own.
let slide5LightText = "onboarding_slide_title5".localized // You can change the text from localized file with your own.
let slide5LigthFontAttributes = [NSAttributedString.Key.font: UIFont.SFProDisplayLight(24)] // You can change also the font.
let slide5LigthAttributedString = NSMutableAttributedString(string: slide5LightText,
                                                                attributes: slide5LigthFontAttributes)
slide5.titleAttributedString = slide5LigthAttributedString
slide5.description = "onboarding_slide5".localized // The description also can be changed.
And all the view models can be called from:
static func getOnboardingViewModels() -> [OnboardingViewModel]
// Return an array of view models, 5 items.

Presenter will transfer the ViewModels in View:
func present() {
   let viewModels = OnboardingViewModel.getOnboardingViewModels()
   view.prepare(with: viewModels)
 }
The UI, formed from UICollectionView is populated through OnboardingDataSource class, in every OnboardingCollectionViewCell. The PageControll current item size can be changed from here:
extension OnboardingViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
  
  internal func collectionView(_ collectionView: UICollectionView,
                               layout collectionViewLayout: UICollectionViewLayout,
                               sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: view.frame.height) /// Change or remove this.
  }
  
  internal func collectionView(_ collectionView: UICollectionView,
                               willDisplay cell: UICollectionViewCell,
                               forItemAt indexPath: IndexPath) {
    pageControl.currentPage = indexPath.row
    pageControl.subviews.forEach { $0.transform = CGAffineTransform(scaleX: 1, y: 1) }
    pageControl.subviews[pageControl.currentPage].transform = CGAffineTransform(scaleX: 1.35, y: 1.35) /// Change or remove this.
  }
}

0

Back To Top

Settings

Items we have showing on Settings:
func getItems() -> [SettingsItemType] {
   return [SettingsItemType.passcode,
           .privatePolicy, .userAgreement,
           .legalNotes, .writeReview,
           .tellAFriend, .suggestFeatures,
           .appVersion]
 }
When the user tap one of them, we have a delegate method which will process the action:
func didSelect(for type: SettingsItemType) {
  switch type {
  case .privatePolicy, .userAgreement, .legalNotes:
  case .passcode, .appVersion:
  case .writeReview:
  case .tellAFriend:
  case .suggestFeatures:

  }
}
We have here injected only configuration manager, to get the properties .plist configurator values:
let configuratorManager = container.resolve(ConfigurationManager.self)!
      
      return SettingsInteractorImp(with: configuratorManager)
Like download App URL, support Email, etc.

0

Back To Top

Extensions

TweetTextField+Extensions

import TweeTextField

/**
 TweeAttributedTextField Extension
 */
extension TweeAttributedTextField {
  
  /**
   Prepare login material texfield
   
   - parameter placeholderText: placeholder info text
   */
  func prepareTextField(with placeholderText: String?) {
    infoTextColor = .errorColor
    infoFontSize = 11.0
    infoAnimationDuration = 0.7
    activeLineColor = .activeLineColor
    lineColor = .defaultPlaceholderColor
    placeholderColor = .blue
    activeLineWidth = 1.0
    lineWidth = 1.0
    minimumPlaceholderFontSize = 10
    originalPlaceholderFontSize = 11.0
    tweePlaceholder = placeholderText
    animationDuration = 0.3
    placeholderDuration = 0.4
    backgroundColor = .clear
    borderStyle = .none
    font = UIFont.SFProRegular(14)
    placeholderColor = .mainColor
  }
}
And:
extension UIViewController {
  
  func validatePassword(_ password: String) -> String? {}
}

0

Back To Top

UIViewController+Alert

How it works: We have:
protocol Loadingable {
  
  func startLoading(with title: String?)
  
  func stopLoading()
  
  func showAlert(error: Error,
                 handler: ((UIAlertAction) -> Void)?)
  
  func showAlert(title: String,
                 error: Error,
                 handler: ((UIAlertAction) -> Void)?)
  
  func showAlert(title: String,
                 message: String,
                 handler: ((UIAlertAction) -> Void)?)
  
  func showAlert(error: Error,
                 buttonName: String?,
                 handler: ((UIAlertAction) -> Void)?)
  
  func showAlertWith(twoOptions message: String?,
                     doneOptionTitle: String?,
                     cancelOptionTitle: String?,
                     cancelCompletion: ((UIAlertAction) -> Void)?,
                     doneCompletion: ((UIAlertAction) -> Void)?)
  
  func showAlertWith(viewModel: Alert,
                     cancelCompletion: ((UIAlertAction) -> Void)?,
                     doneCompletion: ((UIAlertAction) -> Void)?)
}
  • To start loading with title - you can add your own title.
  • Stop loading - cancel
  • Show alert with title and error
  • Show alert with title and message
  • Show alert with error and button name
  • Show alert with 2 options
  • And last - show alert with a view model:
public struct Alert {

    public let title: String?

    public let message: String?

    public let doneOptionTitle: String?

    public let cancelOptionTitle: String?

    public init(title: String?, message: String?, doneOptionTitle: String?, cancelOptionTitle: String?)
}

0

Back To Top

UIViewController+Mail

static func getDeviceInfo() -> String {
   let container = Container(parent: ApplicationServiceBuilder.defaultContainer)
}

func navigateToMail(with email: String, subject: String?) 


and 

extension UIViewController: MFMailComposeViewControllerDelegate {
  
  public func mailComposeController(_ controller: MFMailComposeViewController,
                                    didFinishWith result: MFMailComposeResult,
                                    error: Error?) {
    controller.animationController(.fromBottom, type: .push)
    controller.dismiss(animated: false)
  }
}
We have to DeviceInfo.getDeviceInfo(), where we build the message:
let information = "App Information:\n"
  let appName = "App Name: \(Bundle.appName ?? "")\n"
  let appVersion = "App Version: \(Bundle.fullVersionString)\n\n"
  
  let deviceInformation = "Device information:\n"
  let device = "Device: \(GBDeviceInfo().modelString ?? "")\n"
  let iOSVersion = "iOS Version: \(GBDeviceInfo().osVersion.major)\n"
  let language = "Language: \(Locale.preferredLanguages.first ?? "")\n"
  let timezone = "Timezone: \(TimeZone.current.identifier)\n"
  let connectionStatus = "Connection Status: \(Reachability()?.connection.description ?? "")\n"
Be free to change what you need.

0

Back To Top

UI

RoundButton

This class has an IBInspectable boolean property - isUnderline. If is true - the button will be underlined, if not, it will be with 20px corner radius set in isDefaultShadow - also a bool value.

0

Back To Top

User Interface - UI

LoadingView, PaddingLabel, RoundButton and CircularProgressView.

0

Back To Top

LoadingView

@IBOutlet private weak var nameLabel: UILabel!
@IBOutlet private weak var spinner: UIActivityIndicatorView!


  func setTitle(_ title: String) {
    nameLabel.text = title
  }

containerView.frame = self.bounds
    containerView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
    
    nameLabel.textColor = UIColor.black
    nameLabel.font = UIFont.SFProRegular(17)
    containerView.backgroundColor = .clear
    
    spinner.sizeToFit()
    spinner.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
Those are the setting and configuring the UI from loading view. The loading view is showing when the app makes a request to the server. Spinner will start loading and stops when the request will end.

0

Back To Top

PaddingLabel

We have to add some padding to UILabel:
@IBInspectable var topInset: CGFloat = 4.0
@IBInspectable var bottomInset: CGFloat = 4.0
@IBInspectable var leftInset: CGFloat = 10.0
@IBInspectable var rightInset: CGFloat = 10.0
 

0

Back To Top

Reskin App

Reskin App

To reskin your app we added few configuration extension files, like UIFont and UIColor. Easy to use, just change the property value and your app will be colored in new color.

The app colors are:
class var themeColor: UIColor {
  return UIColor(with: 19, green: 113, blue: 251, alpha: 1.0) // #3664F9
}

class var mainColor: UIColor {
  return UIColor(with: 0, green: 108, blue: 255, alpha: 1.0) // #006CFF
}

class var grayThemeColor: UIColor {
  return UIColor(with: 53, green: 63, blue: 66, alpha: 1.0)
}

class var greenThemeColor: UIColor {
  return UIColor(with: 54, green: 219, blue: 108, alpha: 1.0)
}

class var errorColor: UIColor {
  return UIColor(with: 246, green: 70, blue: 94, alpha: 1.0)
}

class var activeLineColor: UIColor {
  return UIColor(with: 196, green: 207, blue: 209, alpha: 1.0)
}

class var defaultPlaceholderColor: UIColor {
  return UIColor(with: 226, green: 233, blue: 234, alpha: 1.0)
}


and fonts:
class func SFProRegular(_ fontSize: CGFloat) -> UIFont {
  return UIFont(name: "SFProText-Regular", size: fontSize)!
}

class func SFProBold(_ fontSize: CGFloat) -> UIFont {
  return UIFont(name: "SFProText-Bold", size: fontSize)!
}

class func SFProSemibold(_ fontSize: CGFloat) -> UIFont {
  return UIFont(name: "SFProText-Semibold", size: fontSize)!
}

class func SFProDisplayBold(_ fontSize: CGFloat) -> UIFont {
  return UIFont(name: "SFProDisplay-Bold", size: fontSize)!
}

class func SFProDisplayLight(_ fontSize: CGFloat) -> UIFont {
  return UIFont(name: "SFProDisplay-Light", size: fontSize)!
}

class func SFProDisplaySemiBold(_ fontSize: CGFloat) -> UIFont {
  return UIFont(name: "SFProDisplay-SemiBold", size: fontSize)!
}
 

0

Back To Top

How to rename the Project Name

  1. Open Info.plist file and look for Bundle display name, replace with your text.
  2. Navigate to General/Build Settings and search for "Product Name", it will be BarCloud, double click on it and replace with your text.
That's it.

0

Back To Top

App Store

iOS Get Started

Get Started

Welcome!

BarCloud is a powerful app based on Swift 4 that follows the latest Apple's guidelines design principles. This enables us to build consistent user experiences with ease, yet with enough flexibility to support the broad spectrum of Colorado themes. Supercharge your Development and Design workflow today!

 

0

Back To Top

Introduction

The BarCloud guides are step-by-step walkthroughs that help you get started using BarCloud.

0

Back To Top

Installation

Licenses

Currently, on CodeCanyon.net you can get the products with two types of licenses: Single or Extended. If you are making a purchase, be sure to go through the table with the rights and the guidelines, so you can know what is the best fit for you. View the rights table and the description for each license on our by clicking the button below.
See licenses

Purchase and download

In order to get started and use Purpose, you will have to get a license from our official distributor, CodeCanyon.net. Click the button below and then proceed to the next steps. After making your purchase you will receive an email with the download link or you can access it from your Purchases section in your CodeCanyon.Net account.
Purchase now

0

Back To Top

Project structure

Before start, please take a look to this article with Xcode Tutorial for Beginners. This guide will demystify Xcode for you and teach you what you need to know in order to start building apps!   Let's start step by step:
  1. Base Project Files
  2. Helpers - Device info and Store review
  3. UI - Loading View, Padding Label, Round Button and Circular Progress View
  4. Extensions. ViewController+Alert, Mail and TweetTextField
  5. Viper Modules: Splash, Select Option, Onboarding, SignUp, SignIn, ResetPassword, Home, Menu, Items, Search, Settings, WebPreview, QA and Passcode
  6. Supporting Files: Resources - html files for policy, user agreement and legal notes. Fonts - SF-Pro-Display. Sound Scanned.wav file. Localizable.strings - Localization app files - in 14 languages. AppDelegate - The App Life Cycle. Info.plist - the main info app file.
  7. Unit Tests target, UI test target folders
  8. Pods. Podfile is the file where we added the Cocoapods Dependencies. See thee Dependencies Section.
  9. Display Name - Change the app name here with your
  10. Bundle Identifier - Change the bundle identifier with your
  11. Team - Your Apple developer program team
  12. Device orientation - Only Portrait - For iPhone and iPad

0

Back To Top

Project Configuration File

In the Supporting Files Project's Folder, we have the Configuration.plist file. This basically is a property list, commonly abbreviated as plist, is an XML file that contains basic key-value data. You can use a plist in your iOS apps as a simple key-value data store. Let’s find out how that works! More details about how .plist files works we added here.

Raw Configuration file

Let's start line by line: We have this source code of Configuration.plist file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Project</key>
  <dict>
    <key>Support</key>
    <dict>
      <key>EmailSupport</key>
      <string>hi@mistergrizzly.com</string>
    </dict>
    <key>AppStore</key>
    <dict>
      <key>DownloadApp</key>
      <string>https://bit.ly/mistergrizzly</string>
      <key>iTunesURL</key>
      <string>https://itunes.apple.com/app/</string>
      <key>iTunesAppID</key>
      <string>id958625272</string>
    </dict>
    <key>API</key>
    <dict>
      <key>baseSiteId</key>
      <string>site_id</string>
      <key>baseUrl</key>
      <string>https://admin.barcloudapp.com/api</string>
    </dict>
    <key>Features</key>
    <dict>
      <key>certificateHashes</key>
      <array>
        <string>47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=</string>
        <string>S5hcFMQBFyeHWAEr9MkHRteoVDt4sMpeRhG/+PNBW/4=</string>
      </array>
      <key>sslPinningOn</key>
      <false/>
    </dict>
    <key>Headers</key>
    <dict>
      <key>Content-Type</key>
      <string>application/json</string>
    </dict>
  </dict>
</dict>
</plist>

Keys & Values

Project - Is the main key, The Main dictionary:
  • Support - Is the sub-key of the main dictionary
    • EmailSupport is the sub-key of Support key -> your commercial email support for clients, please change it to yours.
  • AppStore - Is the sub-key of the main dictionary
    • DownloadApp is the sub-key of AppStore key -> your commercial download app link support for clients, please change it to yours.
    • iTunesURL is the sub-key of AppStore key -> The app store URL.
    • iTunesAppID is the sub-key of AppStore key -> your commercial App ID from App store, please change it to yours.
  • API- Is the sub-key of the main dictionary
    • baseSiteId is the sub-key of API key -> the main key for API, Don't change it.
    • baseUrl is the sub-key of API key -> the backend API URL, please change it to your address.
  • Features - Is the sub-key of the main dictionary
    • certificateHashes is the sub-key of Features ->Trustkit certificate hashes, how to generate you can find and read here.
      • Item 0 is the first item of certificateHashes -> first pin
      • Item 1 is the second item of certificateHashes -> second pin
    • sslPinningOn is the sub-key of Features key -> Turn on or off the SSL Pinning, Boolean value.
  • Headers - Is the sub-key of the main dictionary
    • Content-Type is the sub-key of Headers key
ℹ️NOTE: Why do we have 2 hashes in the hashes list? TrustKit will actually fail and not let you start with one. The idea is you have to create a backup key, so in case your primary gets compromised, you will have a way to replace the certificates on your server without locking out your users.

0

Back To Top

Requesting Camera Permissions

You need to ask the use permission before access the user private data like camera. In iOS 10 Apple is extending the scope of privacy control. You have to declare in Info.plist file access of any private data. Goto Info.Plist file and added the privacy key according to your requirement.
Camera :
Key       :  Privacy - Camera Usage Description
Value   :  $(PRODUCT_NAME) camera use
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) app requires access to the Camera to take photos.</string>

0

Back To Top

Adding a Custom Font

1. Get Licensed Fonts

Like other intellectual property we need a license to use a font in an app. Just because we have the files for a font doesn’t mean we have permission to use the font in our app. There are a variety of fonts available under the Open Font License from Google Fonts. Many foundries and aggregators also offer fonts that can be licensed for in-app use. For our app we use Apple San Francisco font. You can download it and use.

2. Add Font Files to Xcode Project

Once we have the .ttf or .otf files for our font, the next step is adding them to our Xcode project. I like to use a separate Fonts folder in my project structure. This makes it easier to update the shipping fonts in the app. Copy the font files into the folder, like so: Next drag the Fonts folder from Finder into the Xcode sidebar under the Resources group for your project. When you drop the folder, Xcode will prompt you whether to create a group or a folder reference. Choose folder reference and you’ll be able to adjust the included fonts in Finder while making minimal changes to the Xcode project. Also be sure to check the main target for your app so the Supporting Files, Fonts folder and its content will be copied to your app bundle.

3. Edit Copy Resources Build Phase

Next we’ll double check that Xcode updated the Copy Resources build phase correctly. In your Xcode project, select your main app target and go to the Build Phasessection. Tip open the Copy Bundle Resources build phase. You should see that your new Fonts folder is in the list. (If it’s missing, click the “+” button at the bottom of the phase and choose the folder from the list.)

4. Add Info.plist Keys

There’s just one more configuration step before we can start using our custom fonts. We have to tell iOS about the fonts by editing the app’s Info.plist. Add the UIAppFonts key with an array of strings as the value. Each string should be the relative path to a custom font file in your app bundle. Since we copied the Fonts folder and its contents, we need to include that in the path. Here’s what I added to my Info.plist for the eight font files I’m including in my app:
<key>UIAppFonts</key>
<array>
 <string>SF-Pro-Display-Bold.otf</string>
 <string>SF-Pro-Display-Light.otf</string>
 <string>SF-Pro-Display-Semibold.otf</string>
 <string>SF-Pro-Text-Bold.otf</string>
 <string>SF-Pro-Text-Regular.otf</string>
 <string>SF-Pro-Text-Semibold.otf</string>
</array>


5. Using the Custom Fonts

To use a custom font in code we instantiate it by name. For example:
let size: CGFloat = 17
let font = UIFont.SFProRegular(17)
label.font = font

0

Back To Top

App Transport Security

If your backend will use a https connection, you can skip this part. Only if your connection is http, without secure certificate, please follow the example bellow:

If you opt in to App Transport Security, there is nothing you need to do. App Transport Security is enabled by default for any build created with Xcode 7 and higher. Even though App Transport Security is enabled by default, it can be helpful, for example if you are working in a team, to explicitly define the App Transport Security configuration by adding the following snippet to the target's Info.plist.
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
</dict>
  This block of XML explicitly defines that the application does not allow network request that don't comply with the strict rules defined by App Transport Security. Opting out of App Transport Security is pretty easy as you can see below. The only difference with the previous example is that you explicitly opt out of App Transport Security by adding the following snippet to the target's Info.plist.
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>
It is not recommended to opt out of App Transport Security since Apple plans to require App Transport Security starting 1 January 2017.

More information you can find here.

0

Back To Top

Localization of iOS App

  Localization is simply the process of translating your app into multiple languages. However, before you can localize your app, you first need to internationalize it. Hmm! I know you are asking, “What the heck is internationalization?!” Hold on, you will get the hang of it in a moment. Internationalization is the process of making your app able to adapt to different languages, regions, and culture.

And open Localizable.strings in Chinese (Simplified), then change the “Welcome” text to “欢迎”:

"hello_key" = "欢迎";

Finally, let’s change title = "Welcome" in your View Controller to NSLocalizedString instead of a string literal:

override func viewDidLoad() {    
  super.viewDidLoad()           
    title = "hello_key".localized
}
We pass the lookup key to the first argument here, so it matches whatever localization it is defined which means you can instead write: "home.title" = "hello_key".localized; and localize it to Chinese: "home.title" = "欢迎"; . Then you call NSLocalizedString("home.title", comment: "") and you’ll get the same result. The .localized property, from string extension looks:
/**
 Extension Localization String
 */
public extension String {
  /**
   Localization
   */
  public var localized: String {
    return NSLocalizedString(self, comment: "")
  }
}

0

Back To Top

Dependencies

Like npm for NodeJS or Maven for Java, CocoaPods is a dependency manager for Cocoa projects.
“CocoaPods manages library dependencies for your Xcode projects. The dependencies for your projects are specified in a single text file called a Podfile. CocoaPods will resolve dependencies between libraries, fetch the resulting source code, then link it together in an Xcode workspace to build your project. Ultimately the goal is to improve discoverability of, and engagement in, third party open-source libraries by creating a more centralized ecosystem.” — From CocoaPod’s Website
Project's Podfile:
def app_demo_pods
  
  # Main Pod
  pod 'BarCloudSDK', '~> 1'
  
  # Networking
  pod 'SDWebImage', '~> 4'
  
  # UI
  pod 'Reusable', '~> 4'
  pod 'SwipeCellKit'
  pod 'TPKeyboardAvoiding', '~> 1'
  
  # Local UI Pods
  pod 'TweeTextField', :path => 'BarCloudPods/TweeTextField'
  pod 'Pulley', :path => 'BarCloudPods/Pulley'
  pod 'PasscodeLock', :path => 'BarCloudPods/PasscodeLock'
  
  # Analytics
  pod 'Fabric', '~> 1'
  pod 'Crashlytics', '~> 3'
  pod 'GBDeviceInfo'
  pod 'ReachabilitySwift'
end


target 'Project' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  #use_frameworks!
  use_modular_headers!
  inhibit_all_warnings!

  # Pods for Project
  app_demo_pods

  target 'ProjectTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'ProjectUITests' do
    inherit! :search_paths
    # Pods for testing
  end
  post_install do |installer|
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['SWIFT_VERSION'] = '4.2'
      end
    end
  end
end
 

0

Back To Top

Backend Get Started

Usage

# Demo

Logging In

After db seed in your will be registered:

Username: demo@barcloudapp.com 
Password: 12345

 

List of items

Add new item

Edit an item

 

Search an item

Engine Configuration

User settings

0

Back To Top

Introduction

BarCloud is a powerful app based on Laravel & VueJS. Allowed to store, a big quantity of barcodes from multiple users, export in preferably format (pdf, xls/xlsx, csv), search a barcode in the most popular search engine (google/yandex).  

0

Back To Top

Configuration

# Introduction

When you're ready to deploy your Barcloud application, there are some important things you can do to make sure your application is running and deployed properly.

# Requirements

The Barcloud App has a few system requirements. So, you will need to make sure your server meets the following requirements:

  • PHP >= 7.1.3
  • BCMath PHP Extension
  • Ctype PHP Extension
  • JSON PHP Extension
  • Mbstring PHP Extension
  • OpenSSL PHP Extension
  • PDO PHP Extension
  • Tokenizer PHP Extension
  • XML PHP Extension

# Application Configuration

## Public Directory

After installing, you should configure your web server's document / web root to be the public directory. The index.php in this directory serves as the front controller for all HTTP requests entering your application.

## Directory Permissions

After installing, you may need to configure some permissions. Directories within the storage and the bootstrap/cache directories should be writable by your web server or Laravel will not run.

sudo chmod -R 775 /var/www/html/your-project/storage
sudo chmod -R 775 /var/www/html/your-project/bootstrap/cache
## Application Key

The next thing you should do after installing Laravel is set your application key to a random string. By the php artisan key:generate command.

Typically, this string should be 32 characters long. The key can be set in the .env environment file. If you have not copied the .env.example file to a new file named .env, you should do that now. If the application key is not set, your user sessions and other encrypted data will not be secure!

# Database Configuration

## MySQL

Step1: Create a new database via phpmyadmin

Navigate to PHPMyAdmin, Click on the Databases tab and create a new database with your wanted name.

Clicking on create will create a new database in your MySQL.

Step2: Changes in .env configuration file

Once the database is created, you need to tell your project the details about the database.

All configuration that you should stay private and should not share goes into the .env file of your project.

DB_CONNECTION=mysql
DB_HOST=190.0.0.1
DB_PORT=3066
DB_DATABASE=testProject
DB_USERNAME=youruser
DB_PASSWORD=yourpassword

Modify the following property in your .env file according to your database settings.

## Add search engine configuration

Create a google developer account for a custom search engine https://developers.google.com/custom-search/v1/overview

for yandex here https://xml.yandex.com/

GOOGLE_USERNAME=""
GOOGLE_API_KEY=""
GOOGLE_LANG=""

YANDEX_CX=""
YANDEX_API_KEY=""
YANDEX_LANG=""
## Migrate the database and fill with the default data

Run the database migrations 

php artisan migrate

Run the database seeders

php artisan db:seed

# Web Server Configuration

## Local Development Server

If you have PHP installed locally and you would like to use PHP's built-in development server to serve your application, you may use the serve Artisan command. This command will start a development server at http://localhost:8000:

php artisan serve
## Apache

The project includes a  public/.htaccess file that is used to provide URLs without the index.php front controller in the path. Before serving Barcloud application with Apache, be sure to enable the mod_rewrite module so the .htaccess file will be honored by the server. Follow the following steps.

Step1: Enable the mod rewrite

sudo a2enmod rewrite

Step2: Create the apache configuration file for the barcloudapp

cd /etc/apache2/sites-available
sudo nano barcloud.conf

Step3: Add to the barcloud.conf

<VirtualHost *:80>
    ServerName yourdomain.tld

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/your-project/public

    <Directory /var/www/html/your-project>
        AllowOverride All
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Step4: Enable the application 

sudo a2ensite barcloud.conf 
sudo service apache2 restart
# Nginx

If you are deploying your application to a server that is running Nginx, you may use the following configuration file as a starting point for configuring your web server. 

server {
    listen 80;
    server_name bacloud.com;
    root /path/to/barcloud/public;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    index index.html index.htm index.php;
    charset utf-8;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
    error_page 404 /index.php;
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

 

# Apache Proxy for docker container

If you are deploying your application with docker than here is model of virtual host for apache to expose your docker container

 
 ServerName bacloud.com 
    Allow from localhost
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / http://localhost:8086/
    ProxyPassReverse / https://localhost:8086/

 

# Nginx Proxy for docker container
server {
    server_name barcloud.net;
    listen 80;	
    listen [::]:80;
    location / {
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_redirect off;
      proxy_pass http://localhost:8086/;

    }
}

# Installation using Docker(you must to follow the steps from the Application Configuration)

Go to the project directory

cd barcloud-app

Then start the project using docker composer

docker-compose up -d

Run the database migrations inside of docker container

docker-compose exec application php artisan migrate --seed

Well, your application now is running in a docker container and is available at the url http://localhost:8086/

0

Back To Top

BarCloud iOS SDK

Get Started with SDK

Let's get started with setting up BarCloud SDK in your app to use BarCloud Services, Managers, Models and Extensions.

0

Back To Top

Prerequisites

Before you begin, you need a few things set up in your environment:
  • Xcode 10.1 or later
  • An Xcode project targeting iOS 9 or later
  • Swift projects must use Swift 4.2 or later
  • The bundle identifier of your app
  • CocoaPods 1.4.0 or later

0

Back To Top

Install The SDK

If you are setting up the downloaded project, you need to install the SDK. We distribute our SDK through Cocoapods. You can install Cocoapods by following the installation instructions.

Integration via Cocoapods

  1. Add the following dependencies to your podfile to include BarCloud SDK into your app. It will get pulled in automatically.
     # Use the following line to use BarCloud SDK.
     pod 'BarCloudSDK'
    
  2. Run pod install to install your newly defined pod and open your .xcworkspace.

 ℹ️NOTE:

If you see an error like [!] Unable to find a specification for `BarCloudSDK`while running pod install, please run podrepo update to get the latest pods from Cocoapods repository and then run pod install. Now that you've integrated the frameworks into your application, it's time to start the SDK and make use of the BarCloud App.

0

Back To Top