• Stars
    star
    1,979
  • Rank 23,432 (Top 0.5 %)
  • Language
    Swift
  • License
    MIT License
  • Created almost 5 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A DSL for writing type-safe HTML, XML and RSS in Swift.

Plot

Swift Package Manager Mac + Linux Twitter: @johnsundell

Welcome to Plot, a domain-specific language (DSL) for writing type-safe HTML, XML and RSS in Swift. It can be used to build websites, documents and feeds, as a templating tool, or as a renderer for higher-level components and tools. Itโ€™s primary focus is on static site generation and Swift-based web development.

Plot is used to build and render all of swiftbysundell.com.

Write HTML โ€” in Swift!

Plot enables you to write HTML using native, fully compiled Swift code, by modeling the HTML5 standardโ€™s various elements as Swift APIs. The result is a very lightweight DSL that lets you build complete web pages in a highly expressive way:

let html = HTML(
    .head(
        .title("My website"),
        .stylesheet("styles.css")
    ),
    .body(
        .div(
            .h1("My website"),
            .p("Writing HTML in Swift is pretty great!")
        )
    )
)

Looking at the above, it may at first seem like Plot simply maps each function call directly to an equivalent HTML element โ€” and while thatโ€™s the case for some elements, Plot also inserts many kinds of highly valuable metadata automatically. For example, the above expression will result in this HTML:

<!DOCTYPE html>
<html>
    <head>
        <title>My website</title>
        <meta name="twitter:title" content="My website"/>
        <meta property="og:title" content="My website"/>
        <link rel="stylesheet" href="styles.css" type="text/css"/>
    </head>
    <body>
        <div>
            <h1>My website</h1>
            <p>Writing HTML in Swift is pretty great!</p>
        </div>
    </body>
</html>

As you can see above, Plot added both all of the necessary attributes to load the requested CSS stylesheet, along with additional metadata for the pageโ€™s title as well โ€” improving page rendering, social media sharing, and search engine optimization.

Plot ships with a very wide coverage of the HTML5 standard, enabling all sorts of elements to be defined using the same lightweight syntax โ€” such as tables, lists, and inline text styling:

let html = HTML(
    .body(
        .h2("Countries and their capitals"),
        .table(
            .tr(.th("Country"), .th("Capital")),
            .tr(.td("Sweden"), .td("Stockholm")),
            .tr(.td("Japan"), .td("Tokyo"))
        ),
        .h2("List of ", .strong("programming languages")),
        .ul(
            .li("Swift"),
            .li("Objective-C"),
            .li("C")
        )
    )
)

Above weโ€™re also using Plotโ€™s powerful composition capabilities, which lets us express all sorts of HTML hierarchies by simply adding new elements as comma-separated values.

Applying attributes

Attributes can also be applied the exact same way as child elements are added, by simply adding another entry to an elementโ€™s comma-separated list of content. For example, hereโ€™s how an anchor element with both a CSS class and a URL can be defined:

let html = HTML(
    .body(
        .a(.class("link"), .href("https://github.com"), "GitHub")
    )
)

The fact that attributes, elements and inline text are all defined the same way both makes Plotโ€™s API easier to learn, and also enables a really fast and fluid typing experience โ€” as you can simply type . within any context to keep defining new attributes and elements.

Type safety built-in

Plot makes heavy use of Swiftโ€™s advanced generics capabilities to not only make it possible to write HTML and XML using native code, but to also make that process completely type-safe as well.

All of Plotโ€™s elements and attributes are implemented as context-bound nodes, which both enforces valid HTML semantics, and also enables Xcode and other IDEs to provide rich autocomplete suggestions when writing code using Plotโ€™s DSL.

For example, above the href attribute was added to an <a> element, which is completely valid. However, if we instead attempted to add that same attribute to a <p> element, weโ€™d get a compiler error:

let html = HTML(.body(
    // Compiler error: Referencing static method 'href' on
    // 'Node' requires that 'HTML.BodyContext' conform to
    // 'HTMLLinkableContext'.
    .p(.href("https://github.com"))
))

Plot also leverages the Swift type system to verify each documentโ€™s element structure as well. For example, within HTML lists (such as <ol> and <ul>), itโ€™s only valid to place <li> elements โ€” and if we break that rule, weโ€™ll again get a compiler error:

let html = HTML(.body(
    // Compiler error: Member 'p' in 'Node<HTML.ListContext>'
    // produces result of type 'Node<Context>', but context
    // expects 'Node<HTML.ListContext>'.
    .ul(.p("Not allowed"))
))

This high degree of type safety both results in a really pleasant development experience, and that the HTML and XML documents created using Plot will have a much higher chance of being semantically correct โ€” especially when compared to writing documents and markup using raw strings.

Components

Plotโ€™s Component protocol enables you to define and render higher-level components using a very SwiftUI-like API. Node and Component-based elements can be mixed when creating an HTML document, giving you the flexibility to freely choose which way to implement which part of a website or document.

For example, letโ€™s say that weโ€™re building a news website using Plot, and that weโ€™d like to render news articles in several different places. Hereโ€™s how we could define a reusable NewsArticle component that in turn uses a series of built-in HTML components to render its UI:

struct NewsArticle: Component {
    var imagePath: String
    var title: String
    var description: String

    var body: Component {
        Article {
            Image(url: imagePath, description: "Header image")
            H1(title)
            Span(description).class("description")
        }
        .class("news")
    }
}

As the above example shows, modifiers can also be applied to components to set the value of attributes, such as class or id.

To then integrate the above component into a Node-based hierarchy, we can simply wrap it within a Node using the .component API, like this:

func newsArticlePage(for article: NewsArticle) -> HTML {
    return HTML(.body(
        .div(
            .class("wrapper"),
            .component(article)
        )
    ))
}

You can also directly inline Node-based elements within a componentโ€™s body, which gives you complete freedom to mix and match between the two APIs:

struct Banner: Component {
    var title: String
    var imageURL: URLRepresentable

    var body: Component {
        Div {
            Node.h2(.text(title))
            Image(imageURL)
        }
        .class("banner")
    }
}

Itโ€™s highly recommended that you use the above component-based approach as much as possible when building websites and documents with Plot โ€” as doing so will let you build up a growing library of reusable components, which will most likely accelerate your overall workflow over time.

However, note that the Component API can currently only be used to define elements that appear within the <body> of an HTML page. For <head> elements, or non-HTML elements, the Node-based API always has to be used.

Another important note is that, although Plot has been heavily optimized across the board, Component-based elements do require a bit of extra processing compared to Node-based ones โ€” so in situations where maximum performance is required, you might want to stick to the Node-based API.

Using the component environment

Just like SwiftUI views, Plot components can pass values downwards through a hierarchy using an environment API. Once a value has been entered into the environment using an EnvironmentKey and the environmentValue modifier, it can then be retrieved by defining a property marked with the @EnvironmentValue attribute within a Component implementation.

In the following example, the environment API is used to enable a Page component to assign a given class to all ActionButton components that appear within its hierarchy:

// We start by defining a custom environment key that can be
// used to enter String values into the environment:
extension EnvironmentKey where Value == String {
    static var actionButtonClass: Self {
        Self(defaultValue: "action-button")
    }
}

struct Page: Component {
    var body: Component {
        Div {
            InfoView(title: "...", text: "...")
        }
        // Here we enter a custom action button class
        // into the environment, which will apply to
        // all child components within our above Div:
        .environmentValue("action-button-large",
            key: .actionButtonClass
        )
    }
}

// Our info view doesn't have to have any awareness of
// our environment value. Plot will automatically pass
// it down to the action buttons defined below:
struct InfoView: Component {
    var title: String
    var text: String

    var body: Component {
        Div {
            H2(title)
            Paragraph(text)
            ActionButton(title: "OK")
            ActionButton(title: "Cancel")
        }
        .class("info-view")
    }
}

struct ActionButton: Component {
    var title: String

    // Here we pick up the current environment value for
    // our custom "actionButtonClass" key, which in this
    // example will be the value that our "Page" component
    // entered into the environment:
    @EnvironmentValue(.actionButtonClass) var className

    var body: Component {
        Button(title).class(className)
    }
}

Plot also ships with several components that utilize the environment API for customization. For example, you can change the style of all List components within a hierarchy using the listStyle key/modifier, and the linkRelationship key/modifier lets you tweak the rel attribute of all Link components within a hierarchy.

Inline control flow

Since Plot is focused on static site generation, it also ships with several control flow mechanisms that let you inline logic when using either its Node-based or Component-based APIs. For example, using the .if() command, you can optionally add a node only when a given condition is true, and within a componentโ€™s body, you can simply inline a regular if statement to do the same thing:

let rating: Rating = ...

// When using the Node-based API:
let html = HTML(.body(
    .if(rating.hasEnoughVotes,
        .span("Average score: \(rating.averageScore)")
    )
))

// When using the Component API:
let html = HTML {
    if rating.hasEnoughVotes {
        Span("Average score: \(rating.averageScore)")
    }
}

You can also attach an else clause to the node-based .if() command as well, which will act as a fallback node to be displayed when the commandโ€™s condition is false. You can also use a standard else clause when using the component API:

// When using the Node-based API:
let html = HTML(.body(
    .if(rating.hasEnoughVotes,
        .span("Average score: \(rating.averageScore)"),
        else: .span("Not enough votes yet.")
    )
))

// When using the Component API:
let html = HTML {
    if rating.hasEnoughVotes {
        Span("Average score: \(rating.averageScore)")
    } else {
        Span("Not enough votes yet.")
    }
}

Optional values can also be unwrapped inline using the Node-based .unwrap() command, which takes an optional to unwrap, and a closure used to transform its value into a node. When using the Component-based API, you can simply use a standard if let expression to do the same thing.

Hereโ€™s how those capabilities could be used to conditionally display a part of an HTML page only if a user is logged in.

let user: User? = loadUser()

// When using the Node-based API:
let html = HTML(.body(
    .unwrap(user) {
        .p("Hello, \($0.name)")
    }
))

// When using the Component-based API:
let html = HTML {
    if let user = user {
        Paragraph("Hello, \(user.name)")
    }
}

Just like .if(), the .unwrap() command can also be passed an else clause that will be used if the optional being unwrapped turned out to be nil (and the equivalent logic can once again be implemented using a standard else clause when using the Component-based API):

let user: User? = loadUser()

// When using the Node-based API:
let html = HTML(.body(
    .unwrap(user, {
        .p("Hello, \($0.name)")
    },
    else: .text("Please log in")
    )
))

// When using the Component-based API:
let html = HTML {
    if let user = user {
        Paragraph("Hello, \(user.name)")
    } else {
        Text("Please log in")
    }
}

Finally, the .forEach() command can be used to transform any Swift Sequence into a group of nodes, which is incredibly useful when constructing Node-based lists. When building Component-based lists, you could either directly pass your sequence to the built-in List component, or use a for loop:

let names: [String] = ...

// When using the Node-based API:
let html = HTML(.body(
    .h2("People"),
    .ul(.forEach(names) {
        .li(.class("name"), .text($0))
    })
))

// When using the Component-based API:
let html = HTML {
    H2("People")

    // Passing our array directly to List:
    List(names) { name in
        ListItem(name).class("name")
    }

    // Using a manual for loop within a List closure:
    List {
        for name in names {
            ListItem(name).class("name")
        }
    }
}

Using the above control flow mechanisms, especially when combined with the approach of defining custom components, lets you build really flexible templates, documents and HTML pages โ€” all in a completely type-safe way.

Custom elements and attributes

While Plot aims to cover as much of the standards associated with the document formats that it supports (see โ€œCompatibility with standardsโ€ for more info), chances are that youโ€™ll eventually encounter some form of element or attribute that Plot doesnโ€™t yet cover.

Thankfully, Plot also makes it trivial to define custom elements and attributes โ€” which is both useful when building more free-form XML documents, and as an โ€œescape hatchโ€ when Plot does not yet support a given part of a standard:

// When using the Node-based API:
let html = HTML(.body(
    .element(named: "custom", text: "Hello..."),
    .p(
        .attribute(named: "custom", value: "...world!")
    )
))

// When using the Component-based API:
let html = HTML {
    Element(name: "custom") {
        Text("Hello...")
    }

    Paragraph().attribute(
        named: "custom",
        value: "...world!"
    )
}

While the above APIs are great for constructing one-off custom elements, or for temporary working around a limitation in Plotโ€™s built-in functionality, itโ€™s (in most cases) recommended to instead either:

  • Add and submit the missing API if itโ€™s for an element or attribute that Plot should ideally cover.
  • Define your own type-safe elements and attributes the same way Plot does โ€” by first extending the relevant document format in order to add your own context type, and then extending the Node type with your own DSL APIs:
extension XML {
    enum ProductContext {}
}

extension Node where Context == XML.DocumentContext {
    static func product(_ nodes: Node<XML.ProductContext>...) -> Self {
        .element(named: "product", nodes: nodes)
    }
}

extension Node where Context == XML.ProductContext {
    static func name(_ name: String) -> Self {
        .element(named: "name", text: name)
    }

    static func isAvailable(_ bool: Bool) -> Self {
        .attribute(named: "available", value: String(bool))
    }
}

The above may at first seem like unnecessary busywork, but just like Plot itself, it can really improve the stability and predictability of your custom documents going forward.

Rendering a document

Once youโ€™ve finished constructing a document using Plotโ€™s DSL, call the render method to render it into a String, which can optionally be indented using either tabs or spaces:

let html = HTML(...)

let nonIndentedString = html.render()
let spacesIndentedString = html.render(indentedBy: .spaces(4))
let tabsIndentedString = html.render(indentedBy: .tabs(1))

Individual nodes can also be rendered independently, which makes it possible to use Plot to construct just a single part of a larger document:

let header = Node.header(
    .h1("Title"),
    .span("Description")
)

let string = header.render()

Just like nodes, components can also be rendered on their own:

let header = Header {
    H1("Title")
    Span("Description")
}

let string = header.render()

Plot was built with performance in mind, so regardless of how you render a document, the goal is for that rendering process to be as fast as possible โ€” with very limited node tree traversal and as little string copying and interpolation as possible.

RSS feeds, podcasting, and site maps

Besides HTML and free-form XML, Plot also ships with DSL APIs for constructing RSS and podcast feeds, as well as SiteMap XMLs for search engine indexing.

While these APIs are most likely only relevant when building tools and custom generators (the upcoming static site generator Publish includes implementations of all of these formats), they provide the same level of type safety as when building HTML pages using Plot:

let rss = RSS(
    .item(
        .guid("https://mysite.com/post", .isPermaLink(true)),
        .title("My post"),
        .link("https://mysite.com/post")
    )
)

let podcastFeed = PodcastFeed(
    .title("My podcast"),
    .owner(
        .name("John Appleseed"),
        .email("[email protected]")
    ),
    .item(
        .title("My first episode"),
        .audio(
            url: "https://mycdn.com/episode.mp3",
            byteSize: 79295410,
            title: "My first episode"
        )
    )
)

let siteMap = SiteMap(
    .url(
        .loc("https://mysite.com/post"),
        .lastmod(Date()),
        .changefreq(.daily),
        .priority(1)
    )
)

For more information about what data is required to build a podcast feed, see Appleโ€™s podcasting guide, and for more information about the SiteMap format, see its official spec.

System requirements

To be able to successfully use Plot, make sure that your system has Swift version 5.4 (or later) installed. If youโ€™re using a Mac, also make sure that xcode-select is pointed at an Xcode installation that includes the required version of Swift, and that youโ€™re running macOS Big Sur (11.0) or later.

Please note that Plot does not officially support any form of beta software, including beta versions of Xcode and macOS, or unreleased versions of Swift.

Installation

Plot is distributed using the Swift Package Manager. To install it into a project, simply add it as a dependency within your Package.swift manifest:

let package = Package(
    ...
    dependencies: [
        .package(url: "https://github.com/johnsundell/plot.git", from: "0.9.0")
    ],
    ...
)

Then import Plot wherever youโ€™d like to use it:

import Plot

For more information on how to use the Swift Package Manager, check out this article, or its official documentation.

Navigating Plotโ€™s API and implementation

Plot consists of four core parts, that together make up both its DSL and its overall document rendering API:

  • Node is the core building block for all elements and attributes within any Plot document. It can represent elements and attributes, as well as text content and groups of nodes. Each node is bound to a Context type, which determines which kind of DSL APIs that it gets access to (for example HTML.BodyContext for nodes placed within the <body> of an HTML page).
  • Element represents an element, and can either be opened and closed using two separate tags (like <body></body>) or self-closed (like <img/>). You normally donโ€™t have to interact with this type when using Plot, since you can create instances of it through its DSL.
  • Attribute represents an attribute attached to an element, such as the href of an <a> element, or the src of an <img> element. You can either construct Attribute values through its initializer, or through the DSL, using the .attribute() command.
  • The Component protocol is used to define components in a very SwiftUI-like way. Every component needs to implement a body property, in which its rendered output can be constructed using either other components, or Node-based elements.
  • Document and DocumentFormat represent documents of a given format, such as HTML, RSS and PodcastFeed. These are the top level types that you use in order to start a document building session using Plotโ€™s DSL.

Plot makes heavy use of a technique known as Phantom Types, which is when types are used as โ€œmarkersโ€ for the compiler, to be able to enforce type safety through generic constraints. Both DocumentFormat, and the Context of a node, element or attribute, are used this way โ€” as these types are never instantiated, but rather just there to associate their values with a given context or format.

Plot also uses a very lightweight API design, minimizing external argument labels in favor of reducing the amount of syntax needed to render a document โ€” giving its API a very โ€œDSL-likeโ€ design.

The Component API uses the Result Builders and Property Wrappers language features to bring its very SwiftUI-like API to life.

Compatibility with standards

Plotโ€™s ultimate goal to be fully compatible with all standards that back the document formats that it supports. However, being a very young project, it will most likely need the communityโ€™s help to move it closer to that goal.

The following standards are intended to be covered by Plotโ€™s DSL:

Note that the Component API currently only covers a subset of the HTML 5.0 spec, and can currently only be used to define elements within the <body> of an HTML page.

If you discover an element or attribute thatโ€™s missing, please add it and open a Pull Request with that addition.

Credits, alternatives and focus

Plot was originally written by John Sundell as part of the Publish suite of static site generation tools, which is used to build and generate Swift by Sundell. That suite also includes the Markdown parser Ink, as well as Publish itself.

The idea of using Swift to generate HTML has also been explored by many other people and projects in the community, some of them similar to Plot, some of them completely different. For example Leaf by Vapor, swift-html by Point-Free, and the Swift Talk backend by objc.io. The fact that thereโ€™s a lot of simultaneous innovation within this area is a great thing โ€” since all of these tools (including Plot) have made different decisions around their overall API design and scope, which lets each developer pick the tool that best fits their individual taste and needs (or perhaps build yet another one?).

Plotโ€™s main focus is on Swift-based static site generation, and on supporting a wide range of formats used when building websites, including RSS and podcast feeds. Itโ€™s also tightly integrated with the Publish static site generator, and was built to enable Publish to be as fast and flexible as possible, without having to take on any third-party dependencies. It was open sourced as a separate project both from an architectural perspective, and to enable other tools to be built on top of it without having to depend on Publish.

Contributions and support

Plot is developed completely in the open, and your contributions are more than welcome.

Before you start using Plot in any of your projects, itโ€™s highly recommended that you spend a few minutes familiarizing yourself with its documentation and internal implementation, so that youโ€™ll be ready to tackle any issues or edge cases that you might encounter.

Since this is still a young project, itโ€™s likely to have many limitations and missing features, which is something that can really only be discovered and addressed as more people start using it. While Plot is used in production to build and render all of Swift by Sundell, itโ€™s recommended that you first try it out for your specific use case, to make sure it supports the features that you need.

This project does not come with GitHub Issues-based support, or any other kind of direct support channels, and users are instead encouraged to become active participants in its continued development โ€” by fixing any bugs that they encounter, or by improving the documentation wherever itโ€™s found to be lacking.

If you wish to make a change, open a Pull Request โ€” even if it just contains a draft of the changes youโ€™re planning, or a test that reproduces an issue โ€” and we can discuss it further from there. See Plotโ€™s contribution guide for more information about how to contribute to this project.

Hope youโ€™ll enjoy using Plot!

More Repositories

1

Publish

A static site generator for Swift developers
Swift
4,840
star
2

SwiftTips

A collection of Swift tips & tricks that I've shared on Twitter
3,971
star
3

Files

A nicer way to handle files & folders in Swift
Swift
2,456
star
4

Ink

A fast and flexible Markdown parser written in Swift.
Swift
2,377
star
5

Unbox

[Deprecated] The easy to use Swift JSON decoder
Swift
1,956
star
6

Marathon

[DEPRECATED] Marathon makes it easy to write, run and manage your Swift scripts ๐Ÿƒ
Swift
1,863
star
7

ImagineEngine

A project to create a blazingly fast Swift game engine that is a joy to use ๐Ÿš€
Swift
1,825
star
8

SwiftPlate

Easily generate cross platform Swift framework projects from the command line
Swift
1,766
star
9

Splash

A fast, lightweight and flexible Swift syntax highlighter for blogs, tools and fun!
Swift
1,758
star
10

TestDrive

Quickly try out any Swift pod or framework in a playground
Swift
1,597
star
11

Codextended

Extensions giving Swift's Codable API type inference super powers ๐Ÿฆธโ€โ™‚๏ธ๐Ÿฆนโ€โ™€๏ธ
Swift
1,495
star
12

ShellOut

Easily run shell commands from a Swift script or command line tool
Swift
836
star
13

Wrap

[DEPRECATED] The easy to use Swift JSON encoder
Swift
732
star
14

CollectionConcurrencyKit

Async and concurrent versions of Swiftโ€™s forEach, map, flatMap, and compactMap APIs.
Swift
730
star
15

Sweep

Fast and powerful Swift string scanning made simple
Swift
531
star
16

Playground

Instantly create Swift playgrounds from the command line
Swift
439
star
17

Require

Require optional values to be non-nil, or crash gracefully
Swift
414
star
18

XcodeTheme

My Xcode theme - Sundell's Colors
Swift
408
star
19

AsyncCompatibilityKit

iOS 13-compatible backports of commonly used async/await-based system APIs that are only available from iOS 15 by default.
Swift
378
star
20

Shapeshift

Quickly convert a folder containing Swift files into an iPad-compatible Playground
Swift
338
star
21

Identity

๐Ÿ†” Type-safe identifiers in Swift
Swift
298
star
22

SwiftBySundell

Code samples from the Swift by Sundell website & podcast
Swift
289
star
23

SwiftScripting

A list of Swift scripting tools, frameworks & examples
235
star
24

SuperSpriteKit

Extensions to Apple's SpriteKit game engine
Objective-C
224
star
25

Flow

Operation Oriented Programming in Swift
Swift
217
star
26

Xgen

A Swift package for generating Xcode workspaces & playgrounds
Swift
189
star
27

IndieSupportWeeks

A two-week effort to help support indie developers shipping apps on Apple's platforms who have been financially impacted by the COVID-19 pandemic.
182
star
28

CGOperators

Easily manipulate CGPoints, CGSizes and CGVectors using math operators
Swift
148
star
29

Animate

Declarative UIView animations without nested closures
Swift
129
star
30

SplashPublishPlugin

A Splash plugin for the Publish static site generator
Swift
92
star
31

Assert

A collection of convenient assertions for Swift testing
Swift
69
star
32

UITestingExample

Example code from my blog post about UI testing
Swift
67
star
33

Marathon-Examples

A collection of example Swift scripts that can easily be run using Marathon
Swift
55
star
34

Releases

A Swift package for resolving released versions from a Git repository
Swift
51
star
35

BlockSnippets

Xcode snippets that are very handy when working with blocks in various contexts
51
star
36

PlotPlayground

A Swift playground that comes pre-loaded with Plot, that can be used to explore the new component API.
Swift
49
star
37

SwiftKit

A collection of Swift utilities that I share across my Swift-based projects
Swift
38
star
38

UnitTestingWorkshop

Project used during my workshop "Getting started with unit testing in Swift"
Swift
36
star
39

JSUpdateLookup

A lightweight, easy to use Objective-C class to check if your iOS app has an update available
Objective-C
28
star
40

SwiftAveiro

Skeleton project for my Swift Aveiro workshop "Everyone is an API designer"
Swift
15
star
41

CloudKitChat

A demo chat application powered by CloudKit
Objective-C
14
star
42

swiftbysundell-beta-feedback

Submit your feedback on the Swift by Sundell 2.0 beta
9
star
43

JSGeometry

A set of utility functions that enables easy one-line manipulation of CoreGraphics geometry structs like CGPoint, CGSize & CGRect.
Objective-C
6
star
44

JSAutoCopy

An Objective-C category that enables automatic copying of any object
Objective-C
4
star
45

UnboxDemoPlayground

A Swift Playground that comes setup with Unbox & Wrap, used in my CocoaHeads Stockholm presentation
Swift
3
star
46

JSAutoEncodedObject

Automatically encode or decode any Objective-C object
Objective-C
3
star
47

JSLocalization

An Objective-C class that enables dynamic localization of an iOS app.
Objective-C
3
star
48

MarathonTestScriptWithDependencies

A test script with dependencies - used for Marathon's tests
Swift
2
star
49

JSObservableObject

Easily add protocol-based observation to any Objective-C class
Objective-C
2
star
50

MarathonTestScript

A Swift script that's used in Marathon's tests
Swift
1
star
51

MarathonTestPackage

A Swift package that's used in Marathon's tests
Swift
1
star