Views as functions of their state.

Related tags

Database Few.swift
Overview

Few.swift Carthage compatible

React-inspired library for writing AppKit/UIKit UIs which are functions of their state.1

SwiftBox is used for layout.

Why

UIs are big, messy, mutable, stateful bags of sadness.

Few.swift lets us express UIs as stateless, composable, immutable-ish values of their state. When their state changes, Few.swift calls a function to render the UI for that state, and then intelligently applies any changes.

To put it another way, the state is the necessary complexity of the app. The view is a mapping from state to its representation.

Example

Here's a simple example which counts the number of times a button is clicked:

// This function is called every time `component.updateState` is called.
func renderApp(component: Component<Int>, count: Int) -> Element {
	return View()
		// The view itself should be centered.
		.justification(.Center)
		// The children should be centered in the view.
		.childAlignment(.Center)
		// Layout children in a column.
		.direction(.Column)
		.children([
			Label("You've clicked \(count) times!"),
			Button(title: "Click me!", action: {
					component.updateState { $0 + 1 }
				})
				.margin(Edges(uniform: 10))
				.width(100),
		])
}

class AppDelegate: NSObject, NSApplicationDelegate {
	@IBOutlet weak var window: NSWindow!

	private let appComponent = Component(initialState: 0, render: renderApp)

	func applicationDidFinishLaunching(notification: NSNotification) {
		let contentView = window.contentView as NSView
		appComponent.addToView(contentView)
	}
}

Or a slightly more involved example, a temperature converter:

struct ConverterState {
	static let defaultFahrenheit: CGFloat = 32

	let fahrenheit = defaultFahrenheit
	let celcius = f2c(defaultFahrenheit)
}

private func c2f(c: CGFloat) -> CGFloat {
	return (c * 9/5) + 32
}

private func f2c(f: CGFloat) -> CGFloat {
	return (f - 32) * 5/9
}

private func renderLabeledInput(label: String, value: String, autofocus: Bool, fn: String -> ()) -> Element {
	return View()
		// Layout children in a row.
		.direction(.Row)
		.padding(Edges(bottom: 4))
		.children([
			Label(label).width(75),
			Input(
				text: value,
				placeholder: label,
				action: fn)
				// Autofocus means that the Input will become the first responder when
				// it is first added to the window.
				.autofocus(autofocus)
				.width(100),
		])
}

private func render(component: Component<ConverterState>, state: ConverterState) -> Element {
	let numberFormatter = NSNumberFormatter()
	let parseNumber: String -> CGFloat? = { str in
		return (numberFormatter.numberFromString(str)?.doubleValue).map { CGFloat($0) }
	}
	return View()
		// Center the view.
		.justification(.Center)
		// Center the children.
		.childAlignment(.Center)
		.direction(.Column)
		.children([
			// Each time the text fields change, we re-render. But note that Few.swift
			// is smart enough not to interrupt the user's editing or selection.
			renderLabeledInput("Fahrenheit", "\(state.fahrenheit)", true) {
				if let f = parseNumber($0) {
					component.updateState { _ in ConverterState(fahrenheit: f, celcius: f2c(f)) }
				}
			},
			renderLabeledInput("Celcius", "\(state.celcius)", false) {
				if let c = parseNumber($0) {
					component.updateState { _ in ConverterState(fahrenheit: c2f(c), celcius: c) }
				}
			},
		])
}

This is super cool because the only thing that's mutating is the state. Few.swift is in charge of making an in-place changes to the UI when the state changes.

See FewDemo for some more involved examples.

How does this compare to React Native/ComponentKit?

A few of the most notable differences:

  1. Few.swift is written in... Swift. Type safety is cool.
  2. Single-threaded. React Native and ComponentKit both do layout on a non-main thread. Few.swift keeps everything on the main thread currently.
  3. Both React Native and ComponentKit are battle-tested. They've been used in shipping apps. Few.swift has not.
  4. React Native has an awesome live reload feature.

Quirks

Swift's pretty buggy with concrete subclasses of generic superclasses: https://gist.github.com/joshaber/0978209efef7774393e0. This hurts.

Should I use this?

Probably 🍩 . See above about how it's not battle-tested yet. Pull requests welcome 💖 .

--

1. React, but for Cocoa. A reactive Cocoa, one might say.

Comments
  • iOS Table Header & Footer

    iOS Table Header & Footer

    Here they are – a table view header & footer. With this our iOS table view implementation basically becomes feature-complete, except for editing and animated/incremental updates

    opened by Adlai-Holler 8
  • Swift 2.0 - Xcode 7 Beta 5 (Because I have nothing better to do on a Saturday night)

    Swift 2.0 - Xcode 7 Beta 5 (Because I have nothing better to do on a Saturday night)

    :warning: :warning: DON'T MERGE YET :warning: :warning:

    :warning: :warning: SUPER DON'T MERGE YET :warning: :warning:

    I've been wanting to start a new Swift 2 project and wanted to use Few. So I took some time tonight to try converting everything over. I have no idea if everything works but things compile and both the Mac and iOS schemes build and run

    PS - You probably shouldn't merge this yet (especially not into master)

    opened by joshdholtz 6
  • Basic iOS implementation

    Basic iOS implementation

    This pr adds all the components available for Mac to the iOS target. I don't really know if it is good enough, especially the ScrollView part, but most of the components are pretty the same as their mac version. (also added some bad demo's for testing the components)

    opened by ghost 6
  • How to have overlapping elements?

    How to have overlapping elements?

    Hi, I'm trying to create a cell that looks like

    few-layered-cell-example

    and I need some help getting the small circle into position. Could someone point me in the right direction?

    opened by Adlai-Holler 5
  • Nesting and flexbox

    Nesting and flexbox

    If I create a root app component with 2 child components, those components flexbox layouts are computed first as independent flexbox layout calls and not as children of the app component's layout. In my case that means they receive no width. If instead I wrap them in Elements that are children of the app component it works as expected. Is this by design? Do I need to create wrapping elements around components to handle certain layout cases?

    opened by mrjjwright 5
  • [RFC] Use ComponentSpec as api for defining components

    [RFC] Use ComponentSpec as api for defining components

    Because Swift currently does not support non-generic subclasses, i thought about creating components by implementing a ComponentSpec or ComponentType protocol. We can use associated types to infer the state type. We only need to know when the state changes, maybe we can make a State type that you can observe.

    After implementing ComponentSpec you can use the method construct to create a Component<Spec.StateType>

    To receive lifecycle events we could add an LifeCycleEvent enum, and add lifeCycleStateChanged(event: LifeCycleEvent) for receiving those events.

    opened by ghost 5
  • Carthage install issue on Xcode 7.1?

    Carthage install issue on Xcode 7.1?

    I'm trying to install Few.swift via Carthage and I'm running into some xcodebuild issues. I'm on Xcode 7.1 (7B91b).

    Here's my Cartfile:

    github "joshaber/Few.swift" "swift-2"
    

    Here's the output when building iOS:

    carthage update --platform iOS
    *** Fetching Few.swift
    *** Fetching SwiftBox
    *** Checking out SwiftBox at "c316daebb98237a0c20c5fc7cb92028f44809861"
    *** Checking out Few.swift at "12406946e2b11b3f0848235544ff6aaf97618f6b"
    *** xcodebuild output can be found in /var/folders/s6/qx12s0856zg2zhsnx_14sqd40000gn/T/carthage-xcodebuild.ZyIMzF.log
    *** Building scheme "SwiftBox-iOS" in SwiftBox.xcodeproj
    *** Building scheme "Few-iOS" in Few.xcworkspace
    ** BUILD FAILED **
    
    
    The following build commands failed:
            CompileSwift normal x86_64 /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift
            CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
    (2 failures)
    /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift:69:234: error: type 'Direction' has no member 'Row'
    A shell task failed with exit code 65:
    ** BUILD FAILED **
    
    
    The following build commands failed:
            CompileSwift normal x86_64 /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift
            CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
    (2 failures)
    

    And here's the output when I build for Mac:

    → carthage update --platform Mac
    *** Fetching Few.swift
    *** Fetching SwiftBox
    *** Checking out SwiftBox at "c316daebb98237a0c20c5fc7cb92028f44809861"
    *** Checking out Few.swift at "12406946e2b11b3f0848235544ff6aaf97618f6b"
    *** xcodebuild output can be found in /var/folders/s6/qx12s0856zg2zhsnx_14sqd40000gn/T/carthage-xcodebuild.lbVR1l.log
    *** Building scheme "SwiftBox-Mac" in SwiftBox.xcodeproj
    *** Building scheme "Few-Mac" in Few.xcworkspace
    ** BUILD FAILED **
    
    
    The following build commands failed:
            CompileSwift normal x86_64 /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift
            CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
    (2 failures)
    /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift:39:21: error: type of expression is ambiguous without more context
    /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift:69:234: error: type 'Direction' has no member 'Row'
    A shell task failed with exit code 65:
    ** BUILD FAILED **
    
    
    The following build commands failed:
            CompileSwift normal x86_64 /Users/mark/projects/foo/Carthage/Checkouts/Few.swift/FewCore/Element.swift
            CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
    (2 failures)
    

    FWIW In Carthage/Checkouts/SwiftBox/SwiftBox/Node.swift, I see this:

    public enum Direction: UInt32 {
        case Inherit = 0
        case LeftToRight = 1
        case RightToLeft = 2
    }
    

    No idea if that's the enum it's looking at, but if it is it certainly doesn't have a Row member.

    Is there some mismatch between Few.swift and Swiftbox going on? Some kind of Cartfile confusion or something? Admittedly, I have never used either of these libraries, so I'm exactly sure what I'm looking at. This library looks great - I've got a prototype build of a Redux Swift implementation and would love to hook it up with Few.swift.

    Thoughts?

    opened by mwise 4
  • Add instructions in README on how to edit demo

    Add instructions in README on how to edit demo

    Not sure if it has just been a long week but I'm struggling to get the demo iOS project to update rebuild a framework of my changes :grimacing:

    It looks like it is looking for the Few.framework in a build/Debug-iphoneos directory but that does not exist. I have tried building/running/archiving the framework targets and no frameworks get put there. Is there something I am missing?

    opened by joshdholtz 4
  • Make layout methods public

    Make layout methods public

    In my case this is useful when I'm rolling my own table view cells, so I can get the height of the element before I render it into a view. What good is an incredible UI library if the layout is so opaque?

    opened by Adlai-Holler 4
  • don't crash when realizing an autofocus element outside a window

    don't crash when realizing an autofocus element outside a window

    Sometimes you gotta realize an element outside the window hierarchy. In iOS for example, UIViewController.viewDidLoad is called before the view is added to a window but setting a subview to first responder still works. Probably due to some hackery on Apple's part.

    opened by Adlai-Holler 4
  • Support view-less Elements

    Support view-less Elements

    Currently there's a 1-to-1 correspondence between Elements and views. But that doesn't necessarily have to be the case. A lot of Elements exist in the hierarchy for layout reasons only. They don't actually need to exist as reified views.

    My current thinking is that Element shouldn't create a view but be used only for layout.

    opened by joshaber 2
  • iOS improvements

    iOS improvements

    @joshaber This one's a biggie, sorry. I wanted to get the demo working in decent shape before PR. What we've got is

    • Add Switch element for UISwitch
    • Make Button lay itself out correctly
    • Make Button respect more control states
    • Use isEqualToAttributedString: when possible
    • Add contentInset and scrollIndicatorInsets to TableView
    • Spruce up the demo with a fancy navigation controller

    The changes seemed to have exposed layout bugs in other chunks of the framework, so we'll tackle those later. The layout of elements in the table header is wonky when you switch between demo screens and on first appearance. Curious if you have ideas about the causes there.

    opened by Adlai-Holler 2
  • iOS: table view selection doesn't really work

    iOS: table view selection doesn't really work

    @joshaber We call reloadData() every time we diff a TableView, and that kills our selection. I'll work on this one – just putting it here as a reminder.

    Current thinking is we need to make Element equatable, and not call reloadData() unless our row data has actually changed. Thoughts?

    opened by Adlai-Holler 5
  • Add `didRealize` hook to Component

    Add `didRealize` hook to Component

    @joshaber Sometimes it would be nice to be able to customize a Component's behavior from outside, after the render. For example I want to set some scroll view insets after a component is realized, and subclassing component seems like a bit much. What do you think?

    opened by Adlai-Holler 1
  • Input does not seem to follow

    Input does not seem to follow "stretch" rule

    The Input element does not seem to follow the .childAlignment(.Stretch) property. I have attached a screenshot (I'm bad at designing but these colors are used for clarity of the issue :wink:) and the example view code. I believe my code is right as the "Welcome!" Label and the bottom border views are stretched. BUT please let me know if it is something with just my code :innocent:

    Screenshot (duh)

    ios simulator screen shot jun 23 2015 9 33 48 pm

    Code (even more duh)

    return View(backgroundColor: UIColor.lightGrayColor())
        .justification(.FlexStart)
        .childAlignment(.Center)
        .direction(.Column)
        .children([
    
            View(backgroundColor: UIColor.orangeColor())
                .childAlignment(.Stretch)
                .direction(.Column)
                .width(300)
                .margin(Edges(left: 0, right: 0, bottom: 0, top: 80))
                .padding(Edges(left: 20, right: 20, bottom: 20, top: 20))
                .children([
    
                    // Label
                    { () -> Label in
                        let l = Label("Welcome!",
                            textColor: UIColor.darkGrayColor(),
                            font: UIFont.boldSystemFontOfSize(26))
                            .margin(Edges(left: 0, right: 0, bottom: 20, top: 0))
                        l.selfAlignment(.Center)
                        return l
                    }(),
    
                    // Input - Email
                    Input(textColor: UIColor.blackColor(),
                        placeholder: "Email",
                        keyboardType: UIKeyboardType.EmailAddress,
                        returnKeyType: UIReturnKeyType.Next,
                        borderStyle: UITextBorderStyle.RoundedRect)
                        .margin(Edges(left: 0, right: 0, bottom: 0, top: 0)),
    
                    // Border View
                    View(backgroundColor: UIColor.darkGrayColor())
                        .height(1)
                        .margin(Edges(left: 0, right: 0, bottom: 20, top: 2)),
    
                    // Input - Password
                    Input(textColor: UIColor.blackColor(),
                        placeholder: "Password",
                        returnKeyType: UIReturnKeyType.Done,
                        secure: true),
    
                    // Border View
                    View(backgroundColor: UIColor.darkGrayColor())
                        .height(1)
                        .margin(Edges(left: 0, right: 0, bottom: 10, top: 2))
    
                ])
    
    opened by joshdholtz 5
Releases(0.0.2)
Owner
Josh Abernathy
Josh Abernathy
Safe and easy wrappers for common Firebase Realtime Database functions.

FirebaseHelper FirebaseHelper is a small wrapper over Firebase's realtime database, providing streamlined methods for get, set, delete, and increment

Quan Vo 15 Apr 9, 2022
Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, configurations and app-state. UserDefaults

Prephirences - Preϕrences Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, co

Eric Marchand 557 Nov 22, 2022
A property wrapper for displaying up-to-date database content in SwiftUI views

@Query Latest release: November 25, 2021 • version 0.1.0 • CHANGELOG Requirements: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ • Swift 5.5+ /

Gwendal Roué 102 Dec 29, 2022
PJAlertView - This library is to make your own custom alert views to match your apps look and feel

PJAlertView - This library is to make your own custom alert views to match your apps look and feel

prajeet 6 Nov 10, 2017
Async State Machine aims to provide a way to structure an application thanks to state machines

Async State Machine Async State Machine aims to provide a way to structure an application thanks to state machines. The goal is to identify the states

Thibault Wittemberg 27 Nov 17, 2022
Provides an iOS view controller allowing a user to draw their signature with their finger in a realistic style.

Swift version now available! Mimicking pen-on-paper signatures with a touch screen presents a difficult set of challenges. The rate touch events are e

Uber Open Source 1.3k Jan 6, 2023
Allows users to pull in new song releases from their favorite artists and provides users with important metrics like their top tracks, top artists, and recently played tracks, queryable by time range.

Spotify Radar Spotify Radar is an iOS application that allows users to pull in new song releases from their favorite artists and provides users with i

Kevin Li 630 Dec 13, 2022
SharedImages Screen grabs Main Features Private & self-owned social media Users store their images in their own cloud storage (Dropbox or Google Drive

SharedImages Screen grabs Main Features Private & self-owned social media Users store their images in their own cloud storage (Dropbox or Google Drive

Christopher Prince 12 Feb 10, 2022
A collection of Swift functions, extensions, and SwiftUI and UIKit Views.

J's Helper A collection of Swift functions, extensions, and SwiftUI and UIKit Views. Legend: ?? UIKit ?? SwiftUI ?? Shared Installation In XCode 12 go

Jem Alvarez 3 Oct 1, 2022
SwiftUI views that arrange their children in a flow layout.

SwiftUI Flow SwiftUI views that arrange their children in a flow layout. HFlow A view that arranges its children in a horizontal flow. Usage ScrollVie

Ciaran O'Brien 114 Jan 5, 2023
SwiftUI views that arrange their children in a Pinterest-like layout

SwiftUI Masonry SwiftUI views that arrange their children in a Pinterest-like layout. HMasonry A view that arranges its children in a horizontal mason

Ciaran O'Brien 88 Dec 27, 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
Protocol to handle initial Loadings, Empty Views and Error Handling in a ViewController & views

StatusProvider Protocol to handle initial Loadings, Empty Views and Error Handling in a ViewController & views CocoaPods Podfile pod 'StatusProvider'

Mario Hahn 887 Dec 22, 2022
Swift-picker-views - inline single and multi picker views for UIKit. Without tableview! Easy and simple

swift-picker-views Inline single and multiple picker views for UIKit. No tablevi

IBRAHIM YILMAZ 2 Jan 31, 2022
A Swift utility to make updating table views/collection views trivially easy and reliable.

ArrayDiff An efficient Swift utility to compute the difference between two arrays. Get the removedIndexes and insertedIndexes and pass them directly a

Adlai Holler 100 Jun 5, 2022
GroupWork is an easy to use Swift framework that helps you orchestrate your concurrent, asynchronous functions in a clean and organized way

GroupWork is an easy to use Swift framework that helps you orchestrate your concurrent, asynchronous functions in a clean and organized way. This help

Quan Vo 42 Oct 5, 2022
KeyPathKit is a library that provides the standard functions to manipulate data along with a call-syntax that relies on typed keypaths to make the call sites as short and clean as possible.

KeyPathKit Context Swift 4 has introduced a new type called KeyPath, with allows to access the properties of an object with a very nice syntax. For in

Vincent Pradeilles 406 Dec 25, 2022
Datify 🕛 Easypeasy date functions.

Datify ?? Easypeasy date functions.

Hemang 44 Dec 6, 2022
A collection of functions for statistical calculation written in Swift.

σ (sigma) - statistics library written in Swift This library is a collection of functions that perform statistical calculations in Swift. It can be us

Evgenii Neumerzhitckii 658 Jan 5, 2023
Helper functions for saving text in Keychain securely for iOS, OS X, tvOS and watchOS.

Helper functions for storing text in Keychain for iOS, macOS, tvOS and WatchOS This is a collection of helper functions for saving text and data in th

Evgenii Neumerzhitckii 2.3k Dec 28, 2022