Code Monkey home page Code Monkey logo

jamitfoundation's Introduction

JamitFoundation

Swift Version Platforms License Carthage

JamitFoundation is a lightweight collection of useful concepts to enable data and composition oriented development with UIKit.

Table Of Contents

  1. Motivation
  2. Features
  3. Installation
  4. Documentation
  5. Contributing
  6. License

Motivation

As an agency, working on many different projects with Swift and UIKit brings up new challenges to deliver high quality solutions in reasonable time. It is important to maintain a common sense of code styles and app structure in the development team, but yet it is difficult to merge the collection of knowledge together when the team is split into smaller project groups working on very different topics. JamitFoundation was designed to merge the team knowledge into a reusable core framework which is used in all applications we deliver. It defines a lightweight protocol-oriented concept and structure to write maintainable and reusable user interfaces with UIKit on iOS platforms. All general purpose solutions are combined together in modules and reusable views to improve quality over time and to stop reinventing the wheel. By sharing the framework with the open source community we invite you all to benefit from the usage and share your knowledge with others by contributing to the project.

Features

  • Lightweight, dependency-free core framework
  • Data oriented and declarative view creation and usage
  • Reusable UIKit components
  • Code sharing in views and tableview / collectionview cells
  • Inheritance based view behaviour composition
  • Rich core features and optional plugin modules

Installation

Xcode

  1. Open your project and select File / Swift Packages / Add Package Dependency....
  2. Enter the package URL https://github.com/JamitLabs/JamitFoundation.git press Next and follow the wizard steps.

Swift Package Manager

Add the package dependency to your Package.swift file.

 let package = Package(
    // ...
    dependencies: [
+       .package(url: "https://github.com/JamitLabs/JamitFoundation.git", .upToNextMajor(from: "1.5.2"))
    ]
    // ...
 )

Carthage

Add the following line to your Cartfile.

# A lightweight collection of useful concepts to enable data and composition oriented development with UIKit.
git "[email protected]:JamitLabs/JamitFoundation.git"

CocoaPods

Add the following line to your Podfile.

pod 'JamitFoundation', :git => 'https://github.com/JamitLabs/JamitFoundation.git', :tag => '1.5.2', :inhibit_warnings => true

If you don't want to include all of JamitFoundation, but only specific microframeworks (e.g. PageView), add the following line to your Podfile.

pod 'JamitFoundation/PageView', :git => 'https://github.com/JamitLabs/JamitFoundation.git', :tag => '1.5.2', :inhibit_warnings => true

Documentation

Basics

  1. View Instantiation
  2. StatefulView
  3. StatefulViewController
  4. Cells

View Instantiation

Every view is instantiated by using the extension function instantiate(bundle:owner:options:). This function will automatically check if a nib file with the same name as the view class exists and will load it from the nib file else it will initialize it as usual. By using xib files we can reduce the code size dramatically and also create layouts with visual feedback in Xcode.

Example:

// If a nib file with the name `MyView` exists then it will be instantiated,
// else the default initializer of `UIView` will be used.

// `-instantiate` returns an instance of type `MyView` by inferring the type.
let myView: MyView = .instantiate()

// `-instantiate` returns an instance of type `MyOtherView`.
let mySecondView = MyOtherView.instantiate()

StatefulView

The StatefulView is the base view which contains a generic mutable state of type Model. The model contains all semantical data information required to present the view. Every subclass of the StatefulView is intended to have a mutable state and should only be configured by assigning it's Model. Subviews inside a StatefulView should always be private and not accessible from outside. Callbacks should also be passed as closures inside the Model so that no methods of the view are called from outside. If a custom view is not ment to be subclassed we always define it as final and only leave it open if it is used to compose behaviour into other view classes by using generic parameters.

Example:

import JamitFoundation

struct MyCustomContentViewModel: ViewModelProtocol {
    let title: String

    init(title: String = Self.default.title) {
        self.title = title
    }
}

extension MyCustomContentViewModel {
    static let `default`: Self = .init(title: "")
}

final class MyCustomContentView: StatefulView<MyCustomContentViewModel> {
    private lazy var titleLabel: Label = .instantiate()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Perform view and layout setup logic here...

        addSubview(titleLabel)
    }

    override func didChangeModel() {
        super.didChangeModel()
        
        // Perform view updates based on model data here...

        titleLabel.text = model.title
    }
}

StatefulViewController

The StatefulViewController has the same concept like StatefulView with the difference that it derives from UIViewController instead of UIView.

Example:

import CarouselView
import JamitFoundation
import UIKit

struct CarouselViewControllerModel: ViewModelProtocol {
    let title: String
    let content: CarouselViewModel

    init(
        title: String = Self.default.title,
        content: CarouselViewModel = self.default.content
    ) {
        self.title = title
        self.content = content
    }
}

extension CarouselViewControllerModel {
    static var `default`: Self = .init(title: "", content: .default)
}

final class CarouselViewController: StatefulViewController<CarouselViewControllerModel> {
    private lazy var carouselView: CarouselView<CarouselItemView> = .instantiate()

    override func viewDidLoad() {
        super.viewDidLoad()

        carouselView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(carouselView)
        carouselView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        carouselView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        carouselView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        carouselView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }

    override func didChangeModel() {
        super.didChangeModel()

        title = model.title
        carouselView.model = model.content
    }
}

Cells

Code Sharing

Sharing the code between stateful views and table/collection view cells is straightforward by using the ContainerTableViewCell or ContainerCollectionViewCell which encapsulate a statefulview into a TableViewCell or CollectionViewCell.

Example:

// Only the inheritance is enough to make use of stateful views inside of table views!
final class MyCustomContentTableViewCell: ContainerTableViewCell<MyCustomContentView> {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Perform additional cell initialization code additional to MyCustomContentView logic
    }
        
    // It is still possible to make customizations to the behaviour of the cell by overriding its methods
    override func prepareForReuse() {
        super.prepareForReuse()

        // So additional cleanup on reuse...
    }
}

// Same is possible with collection view cells
final class MyCustomContentCollectionViewCell: ContainerCollectionViewCell<MyCustomContentView> {}
Registration

Cells are registered by using the extension method register(cellOfType:). The behaviour of this method is similar to the -instantiate method and also checks for existing nib files to register them or fallback to register the class.

Example:

override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.register(cellOfType: MyCustomContentTableViewCell.self)
}
Dequeue

Cells can be dequeued by using the extension method dequeue(cellOfType: for:). This function automatically force casts the dequeued cell to the expected type and returns a strongly typed cell.

Example:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeue(cellOfType: MyCustomContentTableViewCell.self, for: indexPath)
    cell.model = model.items[indexPath.row]
    return cell
}

// There are also additional extensions to dequeue in different ways...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return tableView.dequeue(cellOfType: MyCustomContentTableViewCell.self, for: indexPath) { indexPath in 
        return model.items[indexPath.row]
    }
}

Modules

Contributing

We welcome everyone to work with us together delivering helpful tooling to our open source community. Feel free to create an issue to ask questions, give feedback, report bugs or share your new feature ideas. Before creating pull requests please ensure that you have created a related issue ticket.

License

This project is licensed under the terms of the MIT license. See the LICENSE file.

jamitfoundation's People

Contributors

arestronaut avatar fredpi avatar icomputerfreak avatar jamitjona avatar jensk611 avatar jjorn avatar jl-jonas avatar mrylmz avatar raoulschwagmeier avatar simonnumbertwo avatar

Stargazers

 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

jamitfoundation's Issues

Add modifier for composed view wrapping

Add SwiftUI style modifier behaviour to wrap composable views together.

Example:

SomeView()
    .instantiate()
    .onTapGesture { } // Wraps SomeView to ActionView<SomeView> and populates view model somehow

Crash when using BarcodeScanner Plugin

Description

When using the BarcodeScanner without having the camera permission granted the app crashes when dismissing the view the barcode scanner is in.

Steps to reproduce

  1. Open the sample app on the branch feature/sample-project-enhancements on a real device
  2. Decline the camera permission request
  3. Go back to the overview
  4. Open the barcode scanner again. You should be presented with an alert telling you to grant the camera permission
  5. Open the settings
  6. Go back without granting access to the camera
  7. Go back to the overview
    --> App crashes

Add ActionView custom highlighting possibility

Please add the possibility to configure the highlighting for the ActionView implementation. We often need for example an AppStore like scaling effect. Additionally it is not possible to use contentViews with rounded corners since the ActionView is not taking care of it.

Preferred Installation via HTTPS

As described in README, common installation should be done via ssh which is really no need for a public framework. I'd suggest to change the default method to https.

Readme and usage examples

The README should be updated including more detailed information about the usage of JamitFoundation and it would also be very helpful to have an example project or a website describing common use-cases.

Add readme files for modules

To provide useful descriptions and instructions for each module we should add a readme to each module like in WeakCache. The readme of the module should contain an example usage and should be linked to the readme file of the project in `Modules section.

Module-Proposal: Custom TabBar

Just to formally document this.
Add module for a custom TabBar implementation that works on StatefulViews as custom TabBarItem views.

Container cell method interface problem with type inference

The ContainerTableViewCell and ContainerCollectionViewCell provides the methods -viewDidLoad and -didChangeModel as public interface. There is a swift runtime issue which causes a crash because the dynamic method dispatch with generics is failing when the containers are overridden.

Moving those methods into an extension resolved the method dispatch problem but introduces a new problem because it is not allowed to override methods which are declared in an extension.

If could be a solution to remove the methods from the cells but that would be a breaking change for the API.

Add WeakCache implementation

Our WeakCache implementation should be added as micro framework.

  • Tests should be added
  • Example usage should be available

Update view model properties

I was wondering if there is no cleaner way of updating immutable ViewModel properties than doing that inside a StatefulViewController:

var actualModel = self.model
actualModel.isLoading = true
self.model = actualModel

Solution 1

A suggestion would be to have an extension for StatefulViewController containing this:

import JamitFoundation

extension StatefulViewController {
    func updateModel(modifier: (inout Model) -> Void) {
        var actualModel = model
        modifier(&actualModel)
        self.model = actualModel
    }
}

Extension in action:

viewController.updateModel { $0.isLoading = true }

Solution 2

Another approach could be to allow var properties in the ViewModel. Setting a var property would automatically trigger the update mechanism of the StatefulViewController .
Code example:

self.model.isLoading = true // isLoading is a var

Adjust UserDefaults module to work without SwiftUI dependency

The UserDefaults module is using SwiftUI API (Binding) which is only limited by iOS Version but not by availability of SwiftUI dependency. Therefore projects using UserDefaults submodule without linking SwiftUI will not work.

The Api should be encapsulated and should only be available if SwiftUI is available.

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.