Declarative, configurable & highly reusable UI development as making Lego bricks.

Overview

LeeGo

LeeGo is a lightweight Swift framework that helps you decouple & modularise your UI component into small pieces of LEGO style's bricks, to make UI development declarative, configurable and highly reusable.

Rational behind

We all know that MVC pattern has some serious problems when dealing with a complex iOS project. Fortunately there are also a bunch of approaches that aim to fix the problems, most of them mainly address the Controller part, such as MVP, MVVM, MVSM or VIPER. But there is barely a thing which addresses the View part. Does that mean we just run out of all the problems in the View part ? I think the answer is NO, especially when we need our app to be fully responsive.

I’ve talked this idea on a dotSwift’s talk, and also on my blog posts:

Please checkout through for more details.

LeeGo, replaces the View part of MVC by Brick.

LeeGo

Features

What may LeeGo helps you:

  • Describe your whole UI in small pieces of Lego style’s bricks. Let you configure your view as a brick whenever & wherever you want.
  • No longer need to deal with a bunch of custom UIView’s subclasses. Instead, you only need to deal with different Bricks which is lightweight and pure value type.
  • Designed to be UIKit friendly and non-intrusive. There is no need to inherit from other base class at all.
  • Capable to update remotely almost everything via your JSON payload.
  • Built-in convenience methods to make UIStackView like layout hassle-free.
  • Built-in self-sizing mechanism to calculate cell’s height automatically.
  • Method chaining syntax sugar.
  • Benefits from Swift’s enum, let you put the whole UI in a single enum file.

Compare with Facebook ComponentKit

Both:

  • Brings declarative, configurable & reusable UI development for iOS.

Pros:

  • Written in Swift, built for Swift. No more Obj-C++ stuff.
  • Lightweight and UIKit friendly. No inheritance, dealing with standard UIView and Auto Layout directly.
  • Totally smooth to begin with integrating only a small part, also free to drop all without any side effect.
  • Possible to update any part of UI which powered by LeeGo remotely via JSON payload.
  • Powered by standard auto layout which you probably familiar with already.

Cons:

  • Lack of high level features for the moment. Such as support of built-in configurable view controller, view animation, auto layout animation or UIControl component’s action.
  • Powered by standard auto layout which may have some potential performance issues in some circumstances.
  • Still requires the basic knowledge of standard auto layout and Visual Format Language.

Full Documentation

Usages

Basic bricks

Create a Brick instance which named "title" as UILabel, with default text "Showcase" and gray background color

import LeeGo

let titleBrick: Brick = "title".build(UILabel).style([.text("Showcase"), .backgroundColor(.lightGrayColor())])

Configure an UILabel instance just as title brick

let titleLabel = UILabel()
titleLabel.lg_configureAs(titleBrick)

#### More complex bricks Create the bricks inside a cell brick
let titleBrick = "title".build(UILabel).style([.numberOfLines(0), .text("title")])
let subtitleBrick = "subtitle".build(UILabel).style([.textColor(.lightGrayColor()), .numberOfLines(0), .font(.systemFontOfSize(14)), .text("subtitle")])
let imageBrick = "image".build(UIImageView).style([.ratio(1.5), .backgroundColor(.blueColor())]).width(68)

/// Create a brick stand for `UIView` which contains a `title`,
/// a `subtitle` and an `image` inside, layout them with
/// standard auto layout VFL.
let brickName = "cell"
let cellBrick = brickName.build().bricks(titleBrick, subtitleBrick, imageBrick) {
	title, subtitle, image in
	return Layout([
			"H:|-left-[\(image)]-spaceH-[\(title)]-right-|",
			"H:[\(image)]-spaceH-[\(subtitle)]-right-|",
			"V:|-top-[\(title)]-spaceV-[\(subtitle)]-(>=bottom)-|",
			"V:|-top-[\(image)]-(>=bottom)-|"], metrics: LayoutMetrics(20, 20, 20, 20, 10, 10))
}

Dequeue a standard UICollectionViewCell instance, then configure it as cell brick with element as data source

let cell = collectionView.dequeueCell
cell.lg_configureAs(cellBrick, dataSource: element[indexPath.item])

UIStackView inspired layout

Create a brick stand for UIView which contains the 3 bricks (red, green & blue block), then lay them out with the UIStackView inspired layout helper method.

let bricks = ["red".build().style(Style.redBlockStyle).height(50),
 "green".build().style(Style.greenBlockStyle).height(80),
 "blue".build().style(Style.blueBlockStyle).height(30)]

let layout = Layout(bricks: bricks, axis: .Horizontal, align: .Top, distribution: .FillEqually, metrics: LayoutMetrics(10, 10, 10, 10, 10, 10))
let viewBrick = "view".build().style(Style.blocksStyle).bricks(bricks, layout: layout).height(100)

Configure a UIView instance just as the brick

view.lg_configureAs(viewBrick)

#### Union different bricks Union different bricks to a new brick with `UIStackView` style’s layout.
let viewBrick = Brick.union("brickName", bricks: [
		            title,
		            subtitle,
		            Brick.union("blocks", bricks: [
		                redBlock.height(50),
		                greenBlock.height(80),
		                blueBlock.height(30)], axis: .Horizontal, align: .Top, distribution: .FillEqually, metrics: LayoutMetrics(10, 10, 10, 10, 10, 10)).style([.backgroundColor(.yellowColor())])
		            ], axis: .Vertical, align: .Fill, distribution: .Flow(3), metrics: LayoutMetrics(20, 20, 20, 20, 10, 10))
                

Configure a UIView instance just as the brick

view.lg_configureAs(viewBrick)

#### More complex brick and build with an enum An enum which implements `BrickBuilderType`, used to centralize all `brick` designs in a single enum file.
import LeeGo

enum TwitterBrickSet: BrickBuilderType {
    // leaf bricks
    case username, account, avatar, tweetText, tweetImage, date, replyButton, retweetButton, retweetCount, likeButton, likeCount
    
    // complex bricks
    case retweetView, likeView
    case accountHeader, toolbarFooter, retweetHeader
    
    // root bricks
    case standardTweet

    static let brickClass: [Twitter: AnyClass] = [username: UILabel.self, account: UILabel.self, avatar: UIImageView.self, tweetText: UITextView.self]
    
    func brick() -> Brick {
        switch self {
        case .username:
            return build().style([.font(.boldSystemFontOfSize(14))])
        case .account:
            return build().style([.font(.systemFontOfSize(14))])
        case .avatar:
            return build().style([.ratio(1), .backgroundColor(.lightGrayColor()), .cornerRadius(3)]).width(50)
        case .tweetText:
            return build().style([.scrollEnabled(false)])
        
        case .standardTweet:
            return build().style([.backgroundColor(.whiteColor())])
                .bricks(
                    avatar.brick(),
                    accountHeader.brick(),
                    tweetText.brick(),
                    tweetImage.brick(),
                    toolbarFooter.brick()
                ) { (avatar, accountHeader, tweetText, image, toolbarFooter) in
                    Layout(["H:|-10-[\(avatar)]-10-[\(tweetText)]-10-|",
                        "H:[\(avatar)]-10-[\(accountHeader)]-10-|",
                        "H:[\(avatar)]-10-[\(image)]-10-|",
                        "H:[\(avatar)]-10-[\(toolbarFooter)]-10-|",
                        "V:|-10-[\(avatar)]-(>=10)-|",
                        "V:|-10-[\(accountHeader)]-10-[\(tweetText)]-10-[\(image)]-10-[\(toolbarFooter)]-(>=10)-|"])
            }
        }
    }
}

/// Configure your cell
let cell = collectionView.dequeueCell
cell.lg_configureAs(TwitterBrickSet.standardTweet.brick(), dataSource: element[indexPath.item])

## Update UI remotely `Brick` is designed to be JSON convertible, which makes possible that you can control your app’s interface, from tweak some UIKit appearances to create view/cell with brand new design **remotely** via JSON payload. Please check out ["JSON encodable & decodable"](Docs/Remote.md) for more details.

Best practices

For best practices and more design details, please checkout more design details

Installation

CocoaPods

LeeGo is available through CocoaPods. To install it, simply add the following line to your Podfile:

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

pod "LeeGo"

Carthage

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

github "wangshengjia/LeeGo"

Then, run the following command to build the LeeGo framework:

$ carthage update

At last, you need to set up your Xcode project manually to add the LeeGo framework.

Contributing

If you like LeeGo and willing to make it better, you are more than welcomed to send pull request for:

  • Proposing new features.
  • Answering questions on issues.
  • Improving documentation.
  • Reviewing pull requests.
  • Finding, reporting or fixing bugs.

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by [its terms](Docs/Code of Conduct.md).

Well...

If you have any thing want to tell me, please ping me on Twitter, on Weibo or just fire the issue.

I'd like to thank every one who helped me, inspired me and encouraged me. Also thank to my team at Le Monde, especially Vincent & Blaise.

Enjoy~ 🎉 🎉 🍻 🍻

Comments
  • support for xcode 8 beta 5 with swift3

    support for xcode 8 beta 5 with swift3

    @wangshengjia I created this PR just for you to take a look. Two things to mention:

    1. For test dependency (Quick, Nimble), i pointed to my fork to get it compiled. But i didn't fix syntax in test target
    2. There are exceptions raised when i push twitter and details page demo. from this function lg_setupCustomStyle which i haven't looked into.

    Thanks and can't wait to get this more mature to use it. Keep up.

    opened by JackieQi 5
  • Correct the spelling of CocoaPods in README

    Correct the spelling of CocoaPods in README

    This pull request corrects the spelling of CocoaPods 🤓 https://github.com/CocoaPods/shared_resources/tree/master/media

    Created with cocoapods-readme.

    opened by ReadmeCritic 3
  • Brick.bricks is not publicly accessible

    Brick.bricks is not publicly accessible

    It would be nice to iterate over the child bricks like so:

      func update(_ targetView: UIView, with brick: Brick) {
            for child in brick.bricks {
                switch targetView {
                case let label as UILabel where brick == FooBrickBuilder.name:
                    label.text = name
                case let label as UILabel where brick == FooBrickBuilder.address:
                    label.text = address
                default:
                    break
                }
            }
        }
    
    opened by josephquigley 1
  • Fixing Issue 73

    Fixing Issue 73

    Fixing Issue 73 so that it's possible to access the child bricks of a Brick instance.

    Renamed Bricks.bricks to Bricks.childBricks to convey what the developer will be accessing. Exposed UIView.lg_brickName so that you can bind views to bricks with children:

    //Boilerplate for example
    let name = "Example name"
    let address = "Example address"
    
    //BrickDataSource implemented
    func update(_ targetView: UIView, with brick: Brick) {
        brick.childBricks?.forEach { brick in
            let viewsForBrick = ([targetView] + targetView.subviews)
                .filter({$0.lg_brickName == brick.name})
            
            for view in viewsForBrick {
                switch view {
                case let label as UILabel where brick == FooBrickBuilder.name:
                    label.text = name
                case let label as UILabel where brick == FooBrickBuilder.address:
                    label.text = address
                default: ()
                }
            }
        }
    }
    

    Now your UI binding code looks like the following:

    let title = "title".build(UILabel.self).style([.numberOfLines(0), .text("Lorem Ipsum is simply dummy text of the printing industry")])
    let description = "description".build(UILabel.self).style([.textColor(UIColor.lightGray), .numberOfLines(0), .font(UIFont.systemFont(ofSize: 14)), .text("Lorem Ipsum has been the industry's standard dummy text ever since the 1500s")])
    let redBlock = "red".build().style(Style.redBlockStyle)
    let greenBlock = "green".build().style(Style.greenBlockStyle)
    let blueBlock = "blue".build(UIImageView.self).style(Style.blueBlockStyle + [Appearance.custom(["shadowColor": UIColor.brown, "shadowOpacity": Float(1.0)])])
    
    let blocks = Brick.union("blocks", bricks: [
        redBlock.height(50),
        greenBlock.height(80),
        blueBlock.height(30)],
        axis: .horizontal, align: .top, distribution: .fillEqually, metrics: LayoutMetrics(10, 10, 10, 10, 10, 10)).style(Style.blocksStyle)
    
    let brick = "details".build().bricks(title, description, blocks) {
        title, description, blocks in
        Layout(bricks: [title, description, blocks], axis: .vertical, align: .fill, distribution: .flow(3), metrics: LayoutMetrics(84, 20, 20, 20, 10, 10))
    }
    
    self.view.lg_configure(as: brick, updatingStrategy: .always)
    
    // This example is assuming that ClassThatHandlesUpdates would
    // change, say, the text of the UILabel created by the title brick
    ClassThatHandlesUpdates().update(self.view, with: brick)
    
    opened by josephquigley 1
  • Brick.bricks field shares ambiguous name with methods.

    Brick.bricks field shares ambiguous name with methods.

    In Xcode 9.4.1 (Swift 4.1.2) if you try to get the child bricks for a Brick instance, the compiler complains: Ambiguous reference to member 'bricks(_:layout:)'

    Example (using code from DetailsViewController in the Demo App):

    let title = "title".build(UILabel.self).style([.numberOfLines(0), .text("Lorem Ipsum is simply dummy text of the printing industry")])
    let description = "description".build(UILabel.self).style([.textColor(UIColor.lightGray), .numberOfLines(0), .font(UIFont.systemFont(ofSize: 14)), .text("Lorem Ipsum has been the industry's standard dummy text ever since the 1500s")])
    let redBlock = "red".build().style(Style.redBlockStyle)
    let greenBlock = "green".build().style(Style.greenBlockStyle)
    let blueBlock = "blue".build(UIImageView.self).style(Style.blueBlockStyle + [Appearance.custom(["shadowColor": UIColor.brown, "shadowOpacity": Float(1.0)])])
    
    let blocks = Brick.union("blocks", bricks: [
        redBlock.height(50),
        greenBlock.height(80),
        blueBlock.height(30)],
        axis: .horizontal, align: .top, distribution: .fillEqually, metrics: LayoutMetrics(10, 10, 10, 10, 10, 10)).style(Style.blocksStyle)
    

    Then call add the line:

    print(blocks.blocks.count) //Generates compiler error: Ambiguous reference to member 'bricks(_:layout:)'
    
    opened by josephquigley 1
  • Upgraded to Swift 4.

    Upgraded to Swift 4.

    Left out some warnings as they are not critical. 5 test cases fail. Comented some code. Not yet tested if it works.

    I really liked the idea, but do not like the library being in swift 3.

    Also is it supported in any way or is this project abandoned? Anyway i'm going to look into your code and maybe write a simplified bicycle copy of it for myself.

    opened by IgorMuzyka 1
  • Are the explicit types in the README there deliberately?

    Are the explicit types in the README there deliberately?

    eg.

    import LeeGo
    
    let titleBrick: Brick = "title".build(UILabel).style([.text("Showcase"), .backgroundColor(UIColor.lightGrayColor())])
    

    could become

    import LeeGo
    
    let titleBrick: Brick = "title".build(UILabel).style([.text("Showcase"), .backgroundColor(.lightGrayColor())])
    

    and

    let bricks = ["red".build().style(Style.redBlockStyle).height(50),
     "green".build().style(Style.greenBlockStyle).height(80),
     "blue".build().style(Style.blueBlockStyle).height(30)]
    
    let layout = Layout(bricks: bricks, axis: .Horizontal, align: .Top, distribution: .FillEqually, metrics: LayoutMetrics(10, 10, 10, 10, 10, 10))
    let viewBrick = "view".build().style(Style.blocksStyle).bricks(bricks, layout: layout).height(100)
    

    could become

    let bricks = ["red".build().style(.redBlockStyle).height(50),
     "green".build().style(.greenBlockStyle).height(80),
     "blue".build().style(.blueBlockStyle).height(30)]
    
    let layout = Layout(bricks: bricks, axis: .Horizontal, align: .Top, distribution: .FillEqually, metrics: LayoutMetrics(10, 10, 10, 10, 10, 10))
    let viewBrick = "view".build().style(.blocksStyle).bricks(bricks, layout: layout).height(100)
    

    But I understand if this was done intentionally; it is clearer and more easily readable (?) in the examples.

    If this wasn't done deliberately, I'd gladly help using implicit type inference. :)

    opened by BasThomas 1
  • Travis CI configuration needs to be updated

    Travis CI configuration needs to be updated

    When making a pull request, the following error is generated by Travis CI and the build fails: ❌ “Use Legacy Swift Language Version” (SWIFT_VERSION) is required to be configured correctly for targets which use Swift. Use the [Edit > Convert > To Current Swift Syntax…] menu to choose a Swift version or use the Build Settings editor to configure the build setting directly.

    opened by josephquigley 0
  • Refactor Layout to use declarative NSLayoutAnchor

    Refactor Layout to use declarative NSLayoutAnchor

    With the NSLayoutAnchor, we can now use it to avoid the layout format language. Imagine, in your function that defines layout, we could pass the view and return an array of objects. Like this:

    let title = "title".build(UILabel.self).style([.numberOfLines(0), .text("test")])
    let subtitle = "subtitle".build(UILabel.self).style([.numberOfLines(0), .text("subtitle")])
    
    let brick = "details".build().bricks(title) { container, title in
        return [
            title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
            title.centerYAnchor.constraint(equalTo: container.centerYAnchor),
        ]
    }
    

    However, this approach will require some refactoring. I can volunteer, if you want :)

    opened by alickbass 0
  • Component Action

    Component Action

    Hi Wang,

    Congrats on the great tool, nicely done !! I was wondering what's your plan for providing actions for components, like Button, swipe, tap, or anything? Blockable actions?

    question idea 
    opened by freesuraj 3
  • Be able to show/hide one of the component with or without animation

    Be able to show/hide one of the component with or without animation

    Be able to show/hide one of the component (and its relevant margin layout) with or without animation.

    Some inspirations https://github.com/forkingdog/UIView-FDCollapsibleConstraints

    enhancement idea 
    opened by wangshengjia 0
Releases(0.5.1)
Owner
WANG Shengjia
Keep learning...
WANG Shengjia
SwiftUI components and extensions that seem to be highly reusable

SwiftUI components and extensions that seem to be highly reusable

Yusuke Hosonuma 56 Dec 15, 2022
A highly configurable and out-of-the-box-pretty UI library

We absolutely love beautiful interfaces! As an organization named Unicorn, we are obligated to be unique and majestic.

Clayton (McIlrath) Unicorn 225 Feb 11, 2022
A set of frameworks making iOS development more fun

A set of frameworks making iOS development more fun, developed by N8ive Apps Frameworks InterfaceKit AuthKit CoreKit (in progress) NetworkKit (in prog

N8ive apps 18 Oct 12, 2018
A custom reusable circular / progress slider control for iOS application.

HGCircularSlider Example To run the example project, clone the repo, and run pod install from the Example directory first. You also may like HGPlaceho

Hamza Ghazouani 2.4k Jan 6, 2023
Full configurable spreadsheet view user interfaces for iOS applications. With this framework, you can easily create complex layouts like schedule, gantt chart or timetable as if you are using Excel.

kishikawakatsumi/SpreadsheetView has moved! It is being actively maintained at bannzai/SpreadsheetView. This fork was created when the project was mov

Kishikawa Katsumi 34 Sep 26, 2022
Highly customizable Action Sheet Controller with Assets Preview written in Swift

PPAssetsActionController Play with me ▶️ ?? If you want to play with me, just tap here and enjoy! ?? ?? Show me ?? Try me ?? The easiest way to try me

Pavel Pantus 72 Feb 4, 2022
Simple and highly customizable iOS tag list view, in Swift.

TagListView Simple and highly customizable iOS tag list view, in Swift. Supports Storyboard, Auto Layout, and @IBDesignable. Usage The most convenient

Ela Workshop 2.5k Jan 5, 2023
📖 A simple, highly informative page view controller

TL;DR UIPageViewController done properly. ⭐️ Features Simplified data source management & enhanced delegation. Dynamically insert & remove pages. Infi

UI At Six 1.8k Dec 24, 2022
Easy to use, highly customizable gauge view

GDGauge - Customizable Gauge View Requirements Xcode 11+ Swift 5 iOS 9+ Installation Swift Package Manager .package(url: "https://github.com/saeid/GDG

Saeid 74 Dec 5, 2022
📐 Declarative UIKit in 10 lines of code.

Withable ?? Declarative UIKit in 10 lines of code. See corresponding article at Declarative UIKit with 10 lines of code A simple extension instead of

Geri Borbás 14 Dec 20, 2022
🎸🎸🎸 Common categories for daily development. Such as UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.

?????? Common categories for daily development. Such as UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.

77。 423 Jan 4, 2023
A simple GUI for starting/stopping our various local development environments

EnvironmentLauncher A simple GUI for starting/stopping SchoolMint's various local development environments Before you begin, some notes This is a WIP

Francis Scheuermann 1 May 17, 2022
Generates an image that looks like LEGO Art.

LegoArtFilter Generates an image that looks like LEGO Art. This library supports both iOS (14≤) and macOS (11≤). Usage // Get CGImage from CIImage let

Takuto NAKAMURA (Kyome) 10 Jul 7, 2022
Cluster's reusable pre-permissions utility that lets developers ask the users on their own dialog for photos or contacts access, before making the system-based request.

Cluster's reusable pre-permissions utility that lets developers ask the users on their own dialog for photos or contacts access, before making the system-based request. This is based on the Medium post by Cluster describing the different ways to ask for iOS permissions (https://medium.com/p/96fa4eb54f2c).

Riz 1.2k Sep 29, 2022
UIPheonix is a super easy, flexible, dynamic and highly scalable UI framework + concept for building reusable component/control-driven apps for macOS, iOS and tvOS

UIPheonix is a super easy, flexible, dynamic and highly scalable UI framework + concept for building reusable component/control-driven apps for macOS, iOS and tvOS

Mohsan Khan 29 Sep 9, 2022
SwiftUI components and extensions that seem to be highly reusable

SwiftUI components and extensions that seem to be highly reusable

Yusuke Hosonuma 56 Dec 15, 2022
TraceLog is a highly configurable, flexible, portable, and simple to use debug logging system for Swift and Objective-C applications running on Linux, macOS, iOS, watchOS, and tvOS.

Please star this github repository to stay up to date. TraceLog Introduction TraceLog is a highly configurable, flexible, portable, and simple to use

Tony Stone 52 Oct 28, 2022
It is a highly configurable iOS library which allows easy styling with built in styles as well as extra header and footer views so that you can make extremely unique alerts and action sheets.

 CFAlertViewController CFAlertViewController is a library that helps you display and customise Alerts, Action Sheets, and Notifications on iPad and i

Crowdfire Inc. 1.1k Dec 18, 2022
Highly configurable iOS Alert Views with custom content views

NYAlertViewController NYAlertViewController is a replacement for UIAlertController/UIAlertView with support for content views and UI customization. Fe

Nealon Young 609 Nov 20, 2022
Custom & highly configurable seek slider with sliding intervals, disabled state and every possible setting to tackle!

iLabeledSeekSlider Custom & highly configurable seek slider with sliding intervals, disabled state and every possible setting to tackle! Minimum iOS v

Edgar Žigis 9 Aug 16, 2022