{"id":25613562,"url":"https://github.com/sajjon/viewcomposer","last_synced_at":"2025-04-13T19:13:41.673Z","repository":{"id":56925996,"uuid":"92837375","full_name":"Sajjon/ViewComposer","owner":"Sajjon","description":"Compose views using enums swiftly: `let label: UILabel = [.text(\"Hello\"), .textColor(.red)]`","archived":false,"fork":false,"pushed_at":"2018-08-27T14:55:23.000Z","size":71725,"stargazers_count":29,"open_issues_count":3,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T09:52:12.970Z","etag":null,"topics":["enum","styled-components","swift-framework","uikit","view"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sajjon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-30T13:45:48.000Z","updated_at":"2024-10-30T08:15:23.000Z","dependencies_parsed_at":"2022-08-20T22:50:33.100Z","dependency_job_id":null,"html_url":"https://github.com/Sajjon/ViewComposer","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FViewComposer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FViewComposer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FViewComposer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FViewComposer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sajjon","download_url":"https://codeload.github.com/Sajjon/ViewComposer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248766748,"owners_count":21158301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["enum","styled-components","swift-framework","uikit","view"],"created_at":"2025-02-22T01:36:06.600Z","updated_at":"2025-04-13T19:13:41.595Z","avatar_url":"https://github.com/Sajjon.png","language":"Swift","readme":"[![Platform](https://img.shields.io/cocoapods/p/ViewComposer.svg?style=flat)](http://cocoapods.org/pods/ViewComposer)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Version](https://img.shields.io/cocoapods/v/ViewComposer.svg?style=flat)](http://cocoapods.org/pods/ViewComposer)\n[![License](https://img.shields.io/cocoapods/l/ViewComposer.svg?style=flat)](http://cocoapods.org/pods/ViewComposer)\n\n\n# ViewComposer\n\nStyle views using an enum array with its attributes:\n```swift\nlet label: UILabel = [.text(\"Hello World\"), .textColor(.red)]\n```\n\n## Table of Contents\n\u003c!-- MarkdownTOC autolink=\"true\" bracket=\"round\" --\u003e\n\n- [Installation](#installation)\n    - [Swift 4](#swift-4)\n    - [Swift 3](#swift-3)\n- [Style views using enums swiftly](#style-views-using-enums-swiftly)\n- [Non-intrusive - standard UIKit views](#non-intrusive---standard-uikit-views)\n- [Mergeable](#mergeable)\n    - [Examples](#examples)\n    - [Merge operators `\u003c-` and `\u003c\u003c-`](#merge-operators---and--)\n- [Predefined styles](#predefined-styles)\n- [Passing delegates](#passing-delegates)\n- [Supported attributes](#supported-attributes)\n- [SwiftGen support](#swiftgen-support)\n- [CAUTION: Avoid arrays with duplicate values.](#caution-avoid-arrays-with-duplicate-values)\n- [Composables](#composables)\n- [Custom attribute](#custom-attribute)\n    - [Creating a simple custom attribute](#creating-a-simple-custom-attribute)\n    - [Merging custom attributes](#merging-custom-attributes)\n- [Roadmap](#roadmap)\n    - [Architecture/implementation](#architectureimplementation)\n    - [Supported UIKit views](#supported-uikit-views)\n\n\u003c!-- /MarkdownTOC --\u003e\n\n## Installation\n\n### Swift 4\nYou can use the `swift4` branch to use ViewComposer in your Swift 4 code, e.g. with in CocoaPods, the Podfile would be:\n```ruby\npod 'ViewComposer', :git =\u003e 'https://github.com/Sajjon/ViewComposer.git', :branch =\u003e 'swift4'\n```\n\n### Swift 3\n\n#### [CocoaPods](http://cocoapods.org) \n\n```ruby\npod 'ViewComposer', '~\u003e 0.2'\n```\n\n\n#### [Carthage](https://github.com/Carthage/Carthage)\n\n```ogdl\ngithub \"ViewComposer\" ~\u003e 0.2\n```\n\n#### [Swift Package Manager](https://swift.org/package-manager/) \n\n```swift\ndependencies: [\n    .Package(url: \"https://github.com/Sajjon/ViewComposer.git\", majorVersion: 0)\n]\n```\n\n#### Manually\nRefer to famous open source framwork [Alamofire's instructions on how to manually integrate a framework](https://github.com/Alamofire/Alamofire/blob/master/README.md#manually) in order to install ViewComposer in your project manually.\n\n## Style views using enums swiftly\n\nWe are styling our views using an array of the enum type `ViewAttribute` which creates a type called `ViewStyle` which can be used to style our views. **Please note that the order of the attributes (enums) does not matter**:\n\n```swift\nlet label: UILabel = [.text(\"Hello World\"), .textColor(.red)]\nlet same: UILabel = [.textColor(.red), .text(\"Hello World\")] // order does not matter\n```\n\n(*Even though it might be a good idea to use the same order in your app for consistency.*)\n\nThe strength of styling views like this get especially clear when you look at a `UIViewController` example, and this isn't even a complicated ViewController.\n\n```swift\nclass NestedStackViewsViewController: UIViewController {\n\n    lazy var fooLabel: UILabel = [.text(\"Foo\"), .textColor(.blue), .color(.red), .textAlignment(.center)]\n    lazy var barLabel: UILabel =  [.text(\"Bar\"), .textColor(.red), .color(.green), .textAlignment(.center)]\n    lazy var labels: UIStackView = [.views([self.fooLabel, self.barLabel]), .distribution(.fillEqually)]\n    \n    lazy var button: UIButton = [.text(\"Baz\"), .color(.cyan), .textColor(.red)]\n    \n    lazy var stackView: UIStackView = [.views([self.labels, self.button]), .axis(.vertical), .distribution(.fillEqually)]\n    \n\n    ...\n}\n```\n\n**Compared to vanilla:**\n\n```swift\nclass VanillaNestedStackViewsViewController: UIViewController {\n    \n    lazy var fooLabel: UILabel = {\n        let fooLabel = UILabel()\n        fooLabel.translatesAutoresizingMaskIntoConstraints = false\n        fooLabel.text = \"Foo\"\n        fooLabel.textColor = .blue\n        fooLabel.backgroundColor = .red\n        fooLabel.textAlignment = .center\n        return fooLabel\n    }()\n    \n    lazy var barLabel: UILabel = {\n        let barLabel = UILabel()\n        barLabel.translatesAutoresizingMaskIntoConstraints = false\n        barLabel.text = \"Bar\"\n        barLabel.textColor = .red\n        barLabel.backgroundColor = .green\n        barLabel.textAlignment = .center\n        return barLabel\n    }()\n    \n    lazy var labels: UIStackView = {\n        let labels = UIStackView(arrangedSubviews: [self.fooLabel, self.barLabel])\n        labels.translatesAutoresizingMaskIntoConstraints = false\n        labels.distribution = .fillEqually\n        return labels\n    }()\n    \n    lazy var button: UIButton = {\n        let button = UIButton()\n        button.translatesAutoresizingMaskIntoConstraints = false\n        button.backgroundColor = .cyan\n        button.setTitle(\"Baz\", for: .normal)\n        button.setTitleColor(.red, for: .normal)\n        return button\n    }()\n    \n    lazy var stackView: UIStackView = {\n        let stackView = UIStackView(arrangedSubviews: [self.labels, self.button, self.button])\n        stackView.distribution = .fillEqually\n        stackView.translatesAutoresizingMaskIntoConstraints = false\n        stackView.axis = .vertical\n        return stackView\n    }()\n\n    ...\n}\n```\n\n## Non-intrusive - standard UIKit views\nAs we saw in the ViewComposer example above:\n```swift\nlet button: UIButton = [.color(.red), .text(\"Red\"), .textColor(.blue)]\n```\n\n**NO SUBCLASSES NEEDED 🙌**\n\nOf course you can always change your `var` to be `lazy` (recommended) and set attributes on the view which are not yet supported by ViewComposer, like this:\n\n```swift\nlazy var button: UIButton = {\n    let button: UIButton = [.color(.red), .text(\"Red\"), .textColor(.blue)]\n    // setup attributes not yet supported by ViewComposer\n    button.layer.isDoubleSided = false // `isDoubleSided` is not yet supported\n    return button\n}()\n```\n\n## Mergeable\n\nThe attributes enum array `[ViewAttribute]` creates a `ViewStyle` (wrapper that can create views). \n\nAn array `[ViewAttribute]` can be merged with another array or a single attribute. Such an array can also be merged with a `ViewStyle`. A `ViewStyle` can be merged with a single `ViewAttribute` as well. An array of attributes can also be merged with a single attribute. Any type can be on the left handside or right handside in the merge. \n\nThe result of the merge is always a `ViewStyle`, since this is the most refined type.\n\nThere are two different merge functions, `merge:master` and `merge:slave`, since the two types you are merging may contain the same attribute, i.e. there is a duplicate, you need to decide which value to keep.\n\n### Examples\nMerge between `[ViewAttribute]` arrays with a duplicate value using `merge:slave` and `merge:master`\n```swift\nlet foo: [ViewAttribute] = [.text(\"foo\")] // can use `ViewStyle` as `bar`\nlet bar: ViewStyle = [.text(\"bar\"), .color(.red)] // prefer `ViewStyle`\n\n// The merged results are of type `ViewStyle`\nlet fooMerged = foo.merge(slave: bar) // [.text(\"foo\"), .color(.red)]\nlet barMerged = foo.merge(master: bar) // [.text(\"bar\"), .color(.red)]\n```\n\nAs mentioned above, you can merge single attributes as well\n\n```swift\nlet foo: ViewAttribute = .text(\"foo\") \nlet style: ViewStyle = [.text(\"bar\"), .color(.red)]\n\n// The merged results are of type `ViewStyle`\nlet mergeSingleAttribute = style.merge(master: foo) // [.text(\"foo\"), .color(.red)]\n\nlet array: [ViewAttriubte] = [.text(\"foo\")]\nlet mergeArray = style.merge(master: foo) // [.text(\"foo\"), .color(.red)]\n```\n\n#### Optional styles\nYou can also merge optional `ViewStyle`, which is convenient for you initializers\n\n```swift\nfinal class MyViewDefaultingToRed: UIView {\n    init(_ style: ViewStyle? = nil) {\n        let style = style.merge(slave: .default)\n        self.style = style\n        super.init(frame: .zero)\n        setup(with: style) // setup the view using the style..\n    }\n}\nprivate extension ViewStyle {\n    static let `default`: ViewStyle = [.color(.red)]\n}\n```\n\n### Merge operators `\u003c-` and `\u003c\u003c-`\n\nInstead of writing `foo.merge(slave: bar)` we can write `foo \u003c- bar` and instead of writing `foo.merge(master: bar` we can write `foo \u003c\u003c- bar`.\n\n```swift\nlet foo: ViewStyle = [.text(\"foo\")]\nlet bar: ViewStyle = [.text(\"bar\"), .color(.red)]\n\n// The merged results are of type `ViewStyle`\nlet fooMerged = foo \u003c- bar // [.text(\"foo\"), .color(.red)]\nlet barMerged = foo \u003c\u003c- bar // [.text(\"bar\"), .color(.red)]\n```\n\nOf course the operator `\u003c-` and `\u003c\u003c-` works between `ViewStyle`s, `ViewAttribute` and `[ViewAttriubte]` interchangably.\n\nThe operators also works with optional `ViewStyle`. Thus we can rewrite the merge in the initializer of `MyViewDefaultingToRed` using the `merge:slave` operator if we want to:\n\n```swift\nfinal class MyViewDefaultingToRed: UIView {\n    init(_ style: ViewStyle? = nil) {\n        let style = style \u003c- .default\n    ...\n```\n\n#### ViewComposer uses right operator associativty for chained merges\n\nOf course it is possible to chain merges. But when chaining three operands, e.g. \n\n```swift\nlet foo: ViewStyle = ...\nlet bar: ViewStyle = ...\nlet baz: ViewStyle = ...\n\nlet result1 = foo \u003c\u003c- bar \u003c\u003c- baz\nlet result2 = foo \u003c- bar \u003c- baz\nlet result3 = foo \u003c\u003c- bar \u003c- baz\nlet result4 = foo \u003c- bar \u003c\u003c- baz\n```\n\nIn which order shall the merging take place? Should we first merge `foo` with `bar` and then merge that intermediate result with `baz`? Or should we first merge `bar` with `baz` and then merge that intermediate result with `foo`?\n\nThis is called [operator associativity](developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html). In the example above \n\n**Disregarding of left or right associativity** the results **individually**, would have the same result. Meaning that the value of `result1` is the same when using *left* and *right* associativity. The same applies for `result2` and `result3`. \n\n```swift\nlet foo: ViewStyle = [.text(\"foo\")]\nlet bar: ViewStyle = [.text(\"bar\")]\n\n// Associativity irrelevant\nlet result1 = foo \u003c\u003c- bar \u003c\u003c- .text(\"baz\") // result: `[.text(.baz)]`\nlet result2 = foo \u003c- bar \u003c- .text(\"baz\") // result: `[.text(.foo)]`\nlet result3 = foo \u003c\u003c- bar \u003c- .text(\"baz\") // result: `[.text(.bar)]`\n```\n\nBut having a look at this example, **associativity matters!**:\n```swift\nlet foo: ViewStyle = [.text(\"foo\")]\nlet bar: ViewStyle = [.text(\"bar\")]\n\n// Associativy IS relevant\n\n// when using `right` associative:\nlet result4r = foo \u003c- bar \u003c\u003c- .text(\"baz\") // result: `[.text(.foo)]` USED IN ViewComposer\n\n// When using `left` associative:\nlet result4l = foo \u003c- bar \u003c\u003c- .text(\"baz\") // result: `[.text(.baz)]`\n```\n\n`ViewComposer` is using **right** for both the `\u003c-` as well as the `\u003c\u003c-` operator. This means that you should read *from right to left* when values of chained merge operators. \n\n## Predefined styles\n\nYou can also declare some standard style, e.g. `font`, `textColor`, `textAlignment` and upper/case strings that you wanna use for all of your `UILabel`s. Since `ViewStyle` are **mergeable** it makes it convenient to share style between labels and merge custom values into the shared style and creating the label from this merged style.\n\n```swift\nlet style: ViewStyle = [.textColor(.red), .textAlignment(.center)]\nlet fooLabel: UILabel = labelStyle.merge(master: .text(\"Foo\")))\n```\n\nTake another look at the same example as above, but here making use of the `merge(master:`  operator `\u003c\u003c-`:\n\n```swift\nlet labelStyle: ViewStyle = [.textColor(.red), .textAlignment(.center)]\nlet fooLabel: Label = labelStyle \u003c\u003c- .text(\"Foo\")\n```\n\nHere the operator `\u003c\u003c-` actually creates a `UILabel` directly, instead of having to first create a `ViewStyle`. \n\nLet's look at a ViewController example, making use of the strength of predefined styles and the `\u003c\u003c-` operator:\n\n```swift\nprivate let labelStyle: ViewStyle = [.textColor(.red), .textAlignment(.center), .font(.boldSystemFont(ofSize: 30))]\nclass LabelsViewController: UIViewController {\n    \n    private lazy var fooLabel: UILabel = labelStyle \u003c\u003c- .text(\"Foo\")\n    private lazy var barLabel: UILabel = labelStyle \u003c\u003c- [.text(\"Bar\"), .textColor(.blue), .color(.red)]\n    private lazy var bazLabel: UILabel = labelStyle \u003c\u003c- [.text(\"Baz\"), .textAlignment(.left), .color(.green), .font(.boldSystemFont(ofSize: 45))]\n    \n    lazy var stackView: UIStackView = [.views([self.fooLabel, self.barLabel, self.bazLabel]), .axis(.vertical), .distribution(.fillEqually)]\n}\n```\n\n**Compared to vanilla**:\n\n```swift\nclass LabelsViewControllerVanilla: UIViewController {\n    \n    private lazy var fooLabel: UILabel = {\n        let label = UILabel()\n        label.translatesAutoresizingMaskIntoConstraints = false\n        label.text = \"Foo\"\n        label.textColor = .red\n        label.textAlignment = .center\n        label.font = .boldSystemFont(ofSize: 30)\n        return label\n    }()\n    \n    private lazy var barLabel: UILabel = {\n        let label = UILabel()\n        label.translatesAutoresizingMaskIntoConstraints = false\n        label.text = \"Bar\"\n        label.backgroundColor = .red\n        label.textColor = .blue\n        label.textAlignment = .center\n        label.font = .boldSystemFont(ofSize: 30)\n        return label\n    }()\n    \n    private lazy var bazLabel: UILabel = {\n        let label = UILabel()\n        label.translatesAutoresizingMaskIntoConstraints = false\n        label.text = \"Baz\"\n        label.backgroundColor = .green\n        label.textColor = .red\n        label.textAlignment = .left\n        label.font = .boldSystemFont(ofSize: 45)\n        return label\n    }()\n    \n    lazy var stackView: UIStackView = {\n        let stackView = UIStackView(arrangedSubviews: [self.fooLabel, self.barLabel, self.bazLabel])\n        stackView.translatesAutoresizingMaskIntoConstraints = false\n        stackView.distribution = .fillEqually\n        stackView.axis = .vertical\n        return stackView\n    }()\n}\n```\n\n## Passing delegates\n\n```swift\nprivate let height: CGFloat = 50\nprivate let style: ViewStyle = [.font(.big), .height(height)]\nprivate let fieldStyle = style \u003c\u003c- .borderWidth(2)\n\nfinal class LoginViewController: UIViewController {\n    \n    lazy var emailField: UITextField = fieldStyle \u003c\u003c- [.placeholder(\"Email\"), .delegate(self)]\n    lazy var passwordField: UITextField = fieldStyle \u003c\u003c- [.placeholder(\"Password\"), .delegate(self)]\n    \n    // can line break merge\n    lazy var loginButton: UIButton = style \u003c\u003c-\n            .states([Normal(\"Login\", .blue), Highlighted(\"Logging in...\", .red)]) \u003c-\n            .target(self.target(#selector(loginButtonPressed))) \u003c-\n            [.color(.green), .cornerRadius(height/2)]\n    \n    lazy var stackView: UIStackView = .axis(.vertical) \u003c-\n            .views([self.emailField, self.passwordField, self.loginButton]) \u003c-\n            [.spacing(20), .layoutMargins(all: 20), .marginsRelative(true)]\n    \n    ...\n}\n\nextension LoginViewController: UITextFieldDelegate {\n    public func textFieldDidEndEditing(_ textField: UITextField) {\n        textField.validate()\n    }\n}\n\nprivate extension LoginViewController {\n    @objc func loginButtonPressed() {\n        print(\"should login\")\n    }\n}\n```\n\nNote how we pass in `self` as `associated value` to the attribute named `.delegate`, setting the `LoginViewController` class itself as `UITextViewDelegate`. \n\n**Compare to vanilla:**\n\n```swift\nprivate let height: CGFloat = 50\nfinal class VanillaLoginViewController: UIViewController {\n    \n    lazy var emailField: UITextField = {\n        let field = UITextField()\n        field.translatesAutoresizingMaskIntoConstraints = false\n        field.placeholder = \"Email\"\n        field.layer.borderWidth = 2\n        field.font = .big\n        field.delegate = self\n        field.addConstraint(field.heightAnchor.constraint(equalToConstant: height))\n        return field\n    }()\n    \n    lazy var passwordField: UITextField = {\n        let field = UITextField()\n        field.translatesAutoresizingMaskIntoConstraints = false\n        field.placeholder = \"Password\"\n        field.layer.borderWidth = 2\n        field.font = .big\n        field.delegate = self\n        field.addConstraint(field.heightAnchor.constraint(equalToConstant: height))\n        return field\n    }()\n    \n    lazy var loginButton: UIButton = {\n        let button = UIButton()\n        button.translatesAutoresizingMaskIntoConstraints = false\n        button.layer.cornerRadius = height/2\n        button.addConstraint(button.heightAnchor.constraint(equalToConstant: height))\n        button.setTitle(\"Login\", for: .normal)\n        button.setTitle(\"Logging in..\", for: .highlighted)\n        button.setTitleColor(.blue, for: .normal)\n        button.setTitleColor(.red, for: .highlighted)\n        button.backgroundColor = .green\n        button.addTarget(self, action: #selector(loginButtonPressed), for: .primaryActionTriggered)\n        return button\n    }()\n    \n    lazy var stackView: UIStackView = {\n        let stackView = UIStackView(arrangedSubviews: [self.emailField, self.passwordField, self.loginButton])\n        stackView.translatesAutoresizingMaskIntoConstraints = false\n        stackView.axis = .vertical\n        stackView.spacing = 20\n        let margins: CGFloat = 20\n        stackView.layoutMargins = UIEdgeInsets(top: margins, left: margins, bottom: margins, right: margins)\n        stackView.isLayoutMarginsRelativeArrangement = true\n        return stackView\n    }()\n    \n    ...\n}\n\nextension VanillaLoginViewController: UITextFieldDelegate {\n    public func textFieldDidEndEditing(_ textField: UITextField) {\n        textField.validate()\n    }\n}\n\nprivate extension VanillaLoginViewController {\n    @objc func loginButtonPressed() {\n        print(\"should login\")\n    }\n}\n```\n\nWe can also use the `.delegates` attribute for `UITextViewDelegate` and more delegate types are coming.\n\n## Supported attributes\nView the [full of supported attributes list here](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/ViewAttribute/ViewAttribute.swift).\n\n## SwiftGen support\n#### ViewComposer + SwiftGen = ❤️\nThanks to this code snippet from [Olivier Halligon aka \"AliSoftware\"](https://github.com/AliSoftware) - creator of amazing open source project [SwiftGen](https://github.com/SwiftGen/SwiftGen):\n\n```swift\nextension ViewAttribute {\n    static func l10n(_ key: L10n) -\u003e ViewAttribute {\n        return .text(key.string)\n    }\n}\n```\n\nYou can make use of you `L10n` enum cases like this:\n\n```swift\nlet label: UILabel = [.l10n(.helloWorld), .textColor(.red)]\n```\n\nClear, consise and type safe! ⚡️\n\n## CAUTION: Avoid arrays with duplicate values.\nAs of now it is possible to create an attributes array with duplicate values, e.g.\n\n```swift\n// NEVER DO THIS!\nlet foobar: [ViewAttribute] = [.text(\"bar\"), .text(\"foo\")]\n// NOR THIS\nlet foofoo: [ViewAttribute] = [.text(\"foo\"), .text(\"foo\")]\n\n//NOR using style\nlet foobarStyle: Style = [.text(\"bar\"), .text(\"foo\")] // confusing!\n// NOR this\nlet foofooStyle: Style = [.text(\"foo\"), .text(\"foo\")] // confusing!\n```\n\nIt is possible to have an array of attributes containing duplicate values. But using it to instantiate a view, e.g. a `UILabel` will in fact ignore the duplicate value.\n\n```swift\nlet foobar: [ViewAttribute] = [.text(\"foo\"), .text(\"bar\")] //contains both, since array may contain duplicates\nlet label: UILabel = make(foobar) // func `make` calls `let style = attributes.merge(slave: [])` removing duplicates.\nprint(label.text!) // prints \"foo\", since duplicate value `.text(\"bar\")` has been removed by call to `make`\n```\n\nThus it is **strongly** discouraged to instantiate arrays with duplicate values. But the scenarios where you are merging types with duplicates is handled, since you chose which attribute you wanna keep using either `merge:master` or `merge:slave`.\n\n## Composables\nAn alternative to this, if you want to make use of some even more sugary syntax is to use the subclasses conforming to the type `Composable`. You can find some [examples (Label, Button, StackView etc) here](https://github.com/Sajjon/ViewComposer/tree/master/Source/Composables). \n\nIn the current release of ViewComposer in order to use array literal, use must use the caret postfix operator `^` to create your `Composable` types subclassing `UIKit` classes.\n\n```swift\nfinal class Label: UILabel, Composable { ... }\n...\nlet label: Label = [.text(\"foo\")]^ // requires use of `ˆ`\n```\n\n\n## Custom attribute\n\nOne of the attributes is called `custom` taking a `BaseAttributed` type. This is practical if you want to create a view taking some custom attributes.\n\nIn the [example app](https://github.com/Sajjon/ViewComposer/tree/master/Example) you will find two cases of using the `custom` attribute, one simple and one advanced. \n\n### Creating a simple custom attribute\n\nLet's say that you create the custom attribute `FooAttribute`:\n\n#### Step 1: Create attribute enum\n\n```swift\nenum FooAttribute {\n    case foo(String)\n}\n```\n\n#### Step 2 (optional): Protocol for types using custom attribute\n\nLet us then create a shared protocol for all types that what to by styled using the `FooAttribute`, let's call this protocol `FooProtocol`:\n\n```swift\nprotocol FooProtocol {\n    var foo: String? { get set }\n}\n```\n\n#### Step 3 (final): Create style holding list of custom attributes\nIn this example we only declared one attribute (`case`) inside `FooAttribute` but you can of course have multiple. The list of these should be contained in a type conforming to `BaseAttributed`, which requires the `func install(on styleable: Any)`. In this function we style the type with the attributes. Now it becomes clear that it is convenient to not skip **step 2**, and use a protocol bridging all types that can use `FooAttribute` together.\n\n```swift\n\nstruct FooStyle: BaseAttributed {\n    let attributes: [FooAttribute]\n\n    init(_ attributes: [FooAttribute]) {\n        self.attributes = attributes\n    }\n\n    func install(on styleable: Any) {\n        guard var foobar = styleable as? FooProtocol else { return }\n        attributes.forEach {\n            switch $0 {\n            case .foo(let foo):\n                foobar.foo = foo\n            }\n        }\n    }\n}\n```\n\n#### Usage of `FooAttribute`\n\nWe can now create some simple view conforming to `FooProtocol` that we can style using the custom `FooAttribute`. This might be a weird example, since why not just subclass `UILabel` directly? But that would make the code too short and simple, since `UILabel` already conforms to `Styleable`. So it would be misleading and cheating. That is why have this example, since `UIView` does not conform to `Styleable`.\n\n```swift\nfinal class FooLabel: UIView, FooProtocol {\n    typealias Style = ViewStyle\n    var foo: String? { didSet { label.text = foo } } //\n    let label: UILabel\n    \n    init(_ style: ViewStyle? = nil) {\n        let style = style \u003c- .textAlignment(.center)]//default attribute\n        label = style \u003c- [.textColor(.red)] //default textColor\n        super.init(frame: .zero)\n        compose(with: style) // setting up this view and calls `setupSubviews` below\n    }\n}\n\nextension FooLabel: Composable {\n    func setupSubviews(with style: ViewStyle) {\n        addSubview(label) // and add constraints...\n    }\n}\n```\n\nNow we can create and style `FooLabel` with our \"standard\" `ViewAttribute`s but also pass along `FooAttribute` using `custom`, like this:\n\n```swift\nlet fooLabel: FooLabel = [.custom(FooStyle([.foo(\"Foobar\")])), .textColor(.red), .color(.cyan)]\n```\n\nHere we create the `FooLabel` and styling it with our custom `FooStyle` (container for `FooAttribute`) while also styling it with `textColor` and `color`. This way you can combine custom attributes with \"standard\" ones. \n\nWhole code can be found [here in the example app](https://github.com/Sajjon/ViewComposer/blob/master/Example/Source/Views/FooLabel.swift)\n\n**Please note that you cannot merging of custom attributes does not happen automatically**. See next section.\n\n### Merging custom attributes\n\nCheck out [TriangleView.swift](https://github.com/Sajjon/ViewComposer/blob/master/Example/Source/Views/TriangleView.swift) for example of advanced usage of custom attributes.\n\n## Roadmap\n### Architecture/implementation\n- [ ] Change implementation to use Codable/Encodable (Swift 4)?\n- [x] Fix bug where classes conforming to `Composable` and inheriting from superclass conforming to `Makeable` cannot be instantiated using array literals. (requires ues of caret postfix operator `^`)\n\n### Supported UIKit views\n- [x] [UIActivityIndicatorView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIActivityIndicatorView%2BMakeable.swift)\n- [x] [UIButton](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIButton%2BMakeable.swift)\n- [x] [UICollectionView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UICollectionView%2BMakeable.swift)\n- [ ] `UIDatePicker`\n- [x] [UIImageView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIImageView%2BMakeable.swift)\n- [x] [UILabel](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UILabel%2BMakeable.swift)\n- [x] [UIPageControl](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIPageControl%2BMakeable.swift)\n- [x] [UIPickerView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIPickerView%2BMakeable.swift)\n- [x] [UIProgressView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIProgressView%2BMakeable.swift)\n- [ ] `UIScrollView` (Tricky since `UITableView` and `UICollectionView` inherits from it)\n- [x] [UISearchBar](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UISearchBar%2BMakeable.swift)\n- [x] [UISegmentedControl](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UISegmentedControl%2BMakeable.swift)\n- [x] [UISlider](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UISlider%2BMakeable.swift)\n- [x] [UIStackView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIStackView%2BMakeable.swift)\n- [x] [UISwitch](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UISwitch%2BMakeable.swift)\n- [ ] `UITabBar`\n- [x] [UITableView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UITableView%2BMakeable.swift)\n- [x] [UITextField](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UITextField%2BMakeable.swift)\n- [x] [UITextView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UITextView%2BMakeable.swift)\n- [ ] `UIToolbar`\n- [x] [UIWebView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/UIWebView%2BMakeable.swift)\n- [x] [WKWebView](https://github.com/Sajjon/ViewComposer/blob/master/Source/Classes/Views/Makeable/WKWebView%2BMakeable.swift)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fviewcomposer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsajjon%2Fviewcomposer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fviewcomposer/lists"}