UIKit a-là SwiftUI.framework [min deployment target iOS10]

Overview

Render Swift ObjC++ License

Render

CoreRender is a SwiftUI inspired API for UIKit (that is compatible with iOS 10+ and ObjC).

Introduction

  • Declarative: CoreRender uses a declarative API to define UI components. You simply describe the layout for your UI based on a set of inputs and the framework takes care of the rest (diff and reconciliation from virtual view hierarchy to the actual one under the hood).
  • Flexbox layout: CoreRender includes the robust and battle-tested Facebook's Yoga as default layout engine.
  • Fine-grained recycling: Any component such as a text or image can be recycled and reused anywhere in the UI.

TL;DR

Let's build the classic Counter-Example.

The DSL to define the vdom representation is similiar to SwiftUI.

func makeCounterBodyFragment(context: Context, coordinator: CounterCoordinator) -> OpaqueNodeBuilder {
  Component<CounterCoordinator>(context: context) { context, coordinator in
    VStackNode {
      LabelNode(text: "\(coordinator.count)")
        .textColor(.darkText)
        .background(.secondarySystemBackground)
        .width(Const.size + 8 * CGFloat(coordinator.count))
        .height(Const.size)
        .margin(Const.margin)
        .cornerRadius(Const.cornerRadius)
      HStackNode {
        ButtonNode()
          .text("TAP HERE TO INCREASE COUNT")
          .setTarget(coordinator, action: #selector(CounterCoordinator.increase), for: .touchUpInside)
          .background(.systemTeal)
          .padding(Const.margin * 2)
          .cornerRadius(Const.cornerRadius)
      }
    }
    .alignItems(.center)
    .matchHostingViewWidth(withMargin: 0)
  }
}

screen

Label and Button are just specialized versions of the Node<V: UIView> pure function. That means you could wrap any UIView subclass in a vdom node. e.g.

Node(UIScrollView.self) {
  Node(UILabel.self).withLayoutSpec { spec in 
    // This is where you can have all sort of custom view configuration.
  }
  Node(UISwitch.self)
}

The withLayoutSpec modifier allows to specify a custom configuration closure for your view.

Coordinators are the only non-transient objects in CoreRender. They yeld the view internal state and they are able to manually access to the concrete view hierarchy (if one desires to do so).

By calling setNeedsReconcile the vdom is being recomputed and reconciled against the concrete view hiearchy.

class CounterCoordinator: Coordinator{
  var count: UInt = 0

  func incrementCounter() {
    self.count += 1                      // Update the state.
    setNeedsReconcile()                  // Trigger the reconciliation algorithm on the view hiearchy associated to this coordinator.
  }
}

Finally, Components are yet again transient value types that bind together a body fragment with a given coordinator.

class CounterViewCoordinator: UIViewController {
  var hostingView: HostingView!
  let context = Context()

  override func loadView() {
    hostingView = HostingView(context: context, with: [.useSafeAreaInsets]) { context in
      makeCounterBodyFragment(context: context, coordinator: coordinator)
    }
    self.view = hostingView
  }
    
  override func viewDidLayoutSubviews() {
    hostingView.setNeedsLayout()
  }
}

Components can be nested in the node hierarchy.

func makeFragment(context: Context) {
  Component<FooCoordinator>(context: context) { context, coordinator in
    VStackNode {
      LabelNode(text: "Foo")
      Component<BarCoordinator>(context: context) { context, coordinator in
        HStackNode {
          LabelNode(text: "Bar")
          LabelNode(text: "Baz")
        }
      }
    }
  }
}

Use it with SwiftUI

Render nodes can be nested inside SwiftUI bodies by using CoreRenderBridgeView:

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello From SwiftUI")
      CoreRenderBridgeView { context in
        VStackNode {
          LabelNode(text: "Hello")
          LabelNode(text: "From")
          LabelNode(text: "CoreRender")
        }
          .alignItems(.center)
          .background(UIColor.systemGroupedBackground)
          .matchHostingViewWidth(withMargin: 0)
      }
      Text("Back to SwiftUI")
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Credits:

Layout engine:

Comments
  • Event handlers

    Event handlers

    Hi, I am considering to move from storyboards to Render for part of my app UI. Currently everything works fine for me and i managed to build some nice UI with it easily. The only thing that is missing is best practices on how to handle events. in my UI i have two buttons: one for signing in and one for signing up and i wanted to know what is the recommended way to handle the on tap event with Render. Please consider the fact that ReSwift is also big part in my architecture so when the user will click on sign in then i will dispach an action.

    Thanks.

    opened by ranhsd 19
  • TableNode with diff error

    TableNode with diff error

    Hey, I have issue with TableNode again. This is the setup:

        override func render() -> NodeType {
            let table = TableNode(
                reuseIdentifier: "list-table",
                key: "list-table",
                in: self,
                cellReuseEnabled: true,
                autoDiffEnabled: false
            ) { view, layout, size in
                layout.width = size.width
                layout.height = size.height
                
                view.backgroundColor = .clear
            }
            
            let nodes = state.approvals.map { item -> NodeType? in
                do {
                    return try self.nodeFactory.create(for: item, in: self)
                } catch let error {
                    print(error)
                    return nil
                }
            }.flatMap { $0 } // remove nils from array
            
            table.add(children: nodes)
            
            return table
        }
    

    This is also what nodeFactory.create(for:, in:) does:

            let state = GenericApprovalsListItemState(
                id: entity.id,
                title: entity.headline,
                subtitle: entity.subheadline,
                iconName: entity.iconName,
                currency: nil,
                amount: nil,
                itemDescription: entity.itemDescription,
                note: entity.note,
                escalation: .none,
                attachments: nil
            )
            
            let component = GenericApprovalsListItemComponentView()
            
            return ComponentNode(
                component,
                in: parent,
                key: "list-item-midas-\(entity.id)",
                state: state
            )
    

    I am just pushing a new state with updated array of items and adding the children. Every time all of them again - I hope that is right. It ends up with:

    Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (18) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (6 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
    

    Also setting up the contentInset does not seem to work.

    Also is there a recommended way how to add UIRefreshControl?

    opened by zdnk 14
  • Usage for UI components library

    Usage for UI components library

    Hey, I have a question regarding the usage of Render in UI framework for multiple apps. Is it possible to use stateless ComponentViews as usual UIViews without a need to call update?

    For example: if you setup autolayout constraints and the layout is calculated and new frames set, can the ComponentView actually automatically update it-self?

    I want to have a collection of UI components/views/bits available in a separate framework and work with them as usual UIViews without the developer knowing it is actually Render based component. Even if he is going to use them with Render himself.

    opened by zdnk 13
  • [Issue] Uncaught exception : CALayerInvalidGeometry

    [Issue] Uncaught exception : CALayerInvalidGeometry

    I pulled the latest commit and I'm getting this error:

    *** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan nan]' *** First throw call stack:

    been trying to track down the reason. Nothings changed in my code.

    opened by jewilhel 12
  • CollectionNode not displaying all of its cells until scrolled off screen and back again

    CollectionNode not displaying all of its cells until scrolled off screen and back again

    I created an array of card components and configured and rendered them externally, then passed them into my Collection component as nodes where they are added as children to CollectionNode. When I run the app I have 8 cards in the elements array, but only 1 card will display on screen, which is unexpected. If I swipe up and cause the view to scroll upwards so that the non displaying cards move off screen and then back on again, the other cards appear where they are supposed to be.

    I'm guessing the cards reentering the screen cause them to refresh/render whereas they initially did not. I looked for a way to update CollectionNode, but nothing I tried made a difference.

    Here is my code if it helps:

    import Foundation
    import UIKit
    import Render
    
    struct CollectionState: StateType {
        var id: String = "collectionContainer"
        var elements: [NodeType] = [layoutBuilder("")]
        var layoutAs: String = "flow"
    }
    
    class CollectionContainer: ComponentView<CollectionState> {
        required init() {
            super.init()
            self.state = CollectionState()
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("Not supported")
        }
        
        override func render() -> NodeType {
            
            let collection = CollectionNode(key: self.state.id, parent: self, create: {
                let collectionView = UICollectionView(frame: .zero, collectionViewLayout: self.setFlowLayout())
                collectionView.showsHorizontalScrollIndicator = false
                collectionView.showsVerticalScrollIndicator = false
                collectionView.clipsToBounds = false
                collectionView.backgroundColor = UIColor.clear
                collectionView.reloadData()
                return collectionView
                
            }) { (view, layout, size) in
                layout.percent.width = 100%
                layout.percent.height = 100%
                configs[self.state.layoutAs]!(layout)
            }
            return collection.add(children: self.state.elements)
        }
        
        func setFlowLayout() -> UICollectionViewLayout {
            let layout = UICollectionViewFlowLayout()
            layout.sectionInset = UIEdgeInsetsMake(30, 10, 10, 10)
            layout.minimumInteritemSpacing = 10
            layout.minimumLineSpacing = 15
            return layout
        }
    }
    

    You'll notice I'm customizing the collectionView, CollectionNode is using with the create: closure so I can control the layout and set other properties, however not everything works as expected.

    I tried to get access to the cell's view so that I could set clipsToBounds to False, since it clips the shadows off my cards, but I'm not sure how to properly customize cells using CollectionNode. As always your help is so appreciated! :)

    opened by jewilhel 12
  • [Question] CollectionNode horizontal scrolling flow layout

    [Question] CollectionNode horizontal scrolling flow layout

    Hi Alex, I have another question. I've been working with CollectionNode and got it working pretty well displaying a set of cards. I can display them in a grid layout and a vertical scrolling flow layout, but I haven't been able to figure out how to change the layout direction and display them in a horizontal scrolling flow layout like a carousel. Is this possible? I tried quite a few Yoga layout commands but wasn't able to change the flow direction.

    Any help would be appreciated!

    Jason W.

    opened by jewilhel 11
  • [ComponentTableViewCell] Wrong height

    [ComponentTableViewCell] Wrong height

    Hello,

    since TableNode does not support UIRefreshControl for pull to refresh I tried using regular UITableView with ComponentTableViewCell but the height of the cells was not calculated properly :(

    opened by zdnk 10
  • Problem with ScrollView and dynamic content

    Problem with ScrollView and dynamic content

    Hello,

    I have some problems with Node : when the content is changing, the bounds are not updating, leading to hidden content at the bottom of the screen.

    Here is the code

    import UIKit
    import Render
    
    struct ContainerState: StateType {
        var visible: Bool = true
    }
    
    class ScrollTestScreen: ComponentView<ContainerState> {
        override func render() -> NodeType {
            return Node<UIScrollView> { view, layout, size in
                layout.width = size.width
                layout.height = size.height
                layout.flex()
                view.onTap { _ in
                    self.setState { state in
                        state.visible = !state.visible
                    }
                }
            }.add(children: Array(0...(self.state.visible ? 60 : 30)).map { i in
                Node<UILabel>{ view, _, _ in
                    view.text = "FOOBAR \(i)"
                }
            })
        }
    
    }
    
    class ViewController: UIViewController, ComponentController {
        typealias C = ScrollTestScreen
        lazy var component: ScrollTestScreen = ScrollTestScreen()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            addComponentToViewControllerHierarchy()
            renderComponent()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        override func viewDidLayoutSubviews() {
            renderComponent()
        }
    
        func configureComponentProps() {
        }
    }
    

    The conditions are : Render 4.9, iOS 10.3 and iPhone SE

    Is there something I am missing here ?

    Thanks for this wonderful framework !

    opened by chakkrachak 9
  • [Feature Request] React style component Lifecycle hooks

    [Feature Request] React style component Lifecycle hooks

    I was reading through the React documentation and they talked about adding lifecycle hooks to a class: https://facebook.github.io/react/docs/state-and-lifecycle.html

    with componentDidMount() and componentWillUnmount()

    These component methods would be very handy in addition to the willUpdate() and didUpdate() methods already available.

    For example, I want to trigger animations when I create and present a component to the screen and also I want to trigger another just before the component is removed.

    Thanks for your consideration! :)

    opened by jewilhel 9
  • Issue rendering layout with nested components at multiple depths

    Issue rendering layout with nested components at multiple depths

    Hello,

    First off thanks for the AWESOME library! So I recently updated my version of Render from 3.2 to 4.3 which wasn't too bad but now it seems like the Yoga values I set for "layout" seem to now be ignored for any of my nested "children" components! Is this a known issue? I saw that in the notes for the 4.3 release this exact issue may have been fixed but I don't see any relevant code for the fix included with the commit? Any ideas? Cheers! -mo

    opened by msafder 9
  • [TableNode] Pull-to-refresh

    [TableNode] Pull-to-refresh

    So I was playing with the TableNode this whole day and did not manage to add UIRefreshControl so it stays there. Do you think you could implement this feature with TableNode?

    opened by zdnk 8
  • Is Render cross-platform?

    Is Render cross-platform?

    Is logic/code behind Render generic enough to be cross-platform? Is Render tightly coupled with iOS components?

    For example, if I want to render web components or run Render on Android, how can I use (or extend) Render to do that? How is Render compared to SwiftWebUI?

    opened by ngocdaothanh 0
Releases(7.0.3)
Owner
Alex Usbergo
Technical Lead | Sr Software Engineer at Google. Ex-Microsoft/Ex-Skype.
Alex Usbergo
React-inspired framework for building component-based user interfaces in Swift.

TemplateKit React-inspired framework for building component-based user interfaces in Swift. Features ?? Completely native - write your app in Swift ??

Matias Cudich 160 Nov 3, 2022
Puma - A set of build utilities to automate mobile application development and deployment

Puma → https://github.com/onmyway133/Swiftlane Puma is a set of build utilities

Puma Swift 5 Oct 8, 2022
Find who executes a target binary inside your MacOS.

whoexec Whoexec is a tool that will monitor every exec call inside MacOS by using the latest Endpoint Security Framework, with this it's able to detec

Anderson 17 Nov 9, 2022
Extensions which helps to convert objc-style target/action to swifty closures

ActionClosurable Usage ActionClosurable extends UIControl, UIButton, UIRefreshControl, UIGestureRecognizer and UIBarButtonItem. It helps writing swift

takasek 121 Aug 11, 2022
This to learn such as : Add Target , NSNotification Center Send/Get Data , Observer Override , resize Data By Byte , UIImagePicker Delegate , UIAlert Handle , Top ViewController , Get pickerController

Technicalisto How to Create UIButton Class to Pick Data Image Purpose Learn this topics With exact Task Add Target NSNotification Center Send/Get Data

Aya Baghdadi 1 Feb 20, 2022
A set of UIKit helpers that simplify the usage of UIKit view's and controller's in SwiftUI.

A set of UIKit helpers that simplify the usage of UIKit view's and controller's in SwiftUI. Many of these helpers are useful even in a pure UIKit project.

SwiftUI+ 6 Oct 28, 2022
Controls-Practice-UIKit- - Controls Practice (UIKit)

Controls Practice (UIKit) Change a number 0 to 255 different ways: Button (+1) I

null 1 Feb 13, 2022
A lightweight iOS mini framework that enables programmatic navigation with SwiftUI, by using UIKit under the hood.

RouteLinkKit A lightweight iOS mini framework that enables programmatic navigation with SwiftUI. RouteLinkKit is fully compatible with native Navigati

Αθανάσιος Κεφαλάς 4 Feb 8, 2022
This framework allows you to build Table views using UIKit with syntax similar to SwiftUI

This framework allows you to build Table views using UIKit with syntax similar to SwiftUI

Fun 60 Dec 17, 2022
UIEnvironment - A framework that mimics the SwiftUI view's environment to replicate the value distribution thought your UIKit app.

A framework that mimics the SwiftUI view's environment to replicate the value distribution thought your UIKit view hierarchy. Overview D

Łukasz Śliwiński 15 Dec 5, 2022
Neumorphism framework for UIKit.

NeumorphismKit is neumorphism framework for UIKit. Requirements iOS 12.0+ Swift 5.1+ Versions NeumorphismKit version Xcode version 1.0.0 Xcode 11+ 1.1

y-okudera 42 Dec 13, 2022
A reactive, card-based UI framework built on UIKit for iOS developers.

CardParts - made with ❤️ by Intuit: Example Requirements Installation Communication & Contribution Overview Quick Start Architecture CardsViewControll

Intuit 2.5k Jan 4, 2023
Swift extensions for UIKit.framework.

XUIKit Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installation XUIKit is a

FITZ 0 Oct 22, 2021
Mini-application iOS native avec Xcode et Swift exploitant l'architecture MVVM et le framework Combine d'Apple pour la mise en place de la programmation réactive fonctionnelle, le tout avec UIKit.

iOS (Swift 5): Test MVVM avec Combine et UIKit L'architecture MVVM et la programmation réactive fonctionnelle sont très utlisées dans le développement

Koussaïla BEN MAMAR 2 Nov 5, 2022
iOS native app demo with Xcode and Swift using MVVM architecture and Apple's Combine framework for functional reactive programming, all with UIKit

iOS (Swift 5): MVVM test with Combine and UIKit MVVM and functional reactive programming (FRP) are very used in iOS development inside companies. Here

Koussaïla BEN MAMAR 2 Dec 31, 2021
A simple keyframe-based animation framework for UIKit. Perfect for scrolling app intros.

Jazz Hands is a simple keyframe-based animation framework for UIKit. Animations can be controlled via gestures, scroll views, KVO, or ReactiveCocoa. J

IFTTT 6.4k Dec 28, 2022
LemonadeDeclarativeUI framework contains some new functions for UIKit

LemonadeDeclarativeUI framework contains some new functions for UIKit. This library has been developing. If you want to contribute reach me!

Özgür Elmaslı 0 Jan 2, 2022
Using the UIKitChain framework, You can create a UIKit component in one line of code.

Using the UIKitChain framework, You can create a UIKit component in one line of code. Installation CocoaPods CocoaPods is a dependency manager for Coc

Malith Nadeeshan 1 Sep 1, 2022
A Set of Tools To Extend UIKit (Classic iOS Framework)

RVS_UIKit_Toolbox A set of basic UIKit tools, for Swift iOS app development. Overview This package offers a few extensions of standard UIKit classes,

The Great Rift Valley Software Company 2 Jul 8, 2022
content for Using Combine - notes on learning Combine with UIKit and SwiftUI

SwiftUI-Notes A collection of notes, project pieces, playgrounds and ideas on learning and using SwiftUI and Combine. Changes, corrections, and feedba

Joseph Heck 1.7k Dec 27, 2022