Skip to content
Permalink
Browse files

Add TextView host component (#76)

* Add TextView host component

* Refactor TextViewExample

* Add TextViewDelegate to TextView

* Add applyScrollOptions to TextView

* Apply swiftformat
  • Loading branch information...
hodovani committed Mar 29, 2019
1 parent b2cd397 commit 822d527a7568fc04c117dc60f66ab3b338effdf1
@@ -23,6 +23,7 @@
A67717202226DC7C0028A6F3 /* Gameboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A677171F2226DC7C0028A6F3 /* Gameboard.swift */; };
A67717222226E7FD0028A6F3 /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67717212226E7FD0028A6F3 /* Menu.swift */; };
A69E588322490836000874F8 /* TabContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E588222490836000874F8 /* TabContent.swift */; };
A69E5891224BE4BE000874F8 /* TextViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E5890224BE4BE000874F8 /* TextViewExample.swift */; };
A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; };
A6D6538122312263007FA886 /* CollectionExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D6538022312263007FA886 /* CollectionExample.swift */; };
A6FEF7952227C1CC008BB292 /* Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FEF7942227C1CC008BB292 /* Scroll.swift */; };
@@ -74,6 +75,7 @@
A677171F2226DC7C0028A6F3 /* Gameboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gameboard.swift; sourceTree = "<group>"; };
A67717212226E7FD0028A6F3 /* Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; };
A69E588222490836000874F8 /* TabContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabContent.swift; sourceTree = "<group>"; };
A69E5890224BE4BE000874F8 /* TextViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewExample.swift; sourceTree = "<group>"; };
A6D5AF86221B131400DBF186 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
A6D6538022312263007FA886 /* CollectionExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExample.swift; sourceTree = "<group>"; };
A6FEF7942227C1CC008BB292 /* Scroll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroll.swift; sourceTree = "<group>"; };
@@ -239,6 +241,7 @@
A675A5962239010D005173E5 /* TabExample.swift */,
D1B9056F224258AC0077CD5E /* Throbber.swift */,
A69E588222490836000874F8 /* TabContent.swift */,
A69E5890224BE4BE000874F8 /* TextViewExample.swift */,
);
path = Components;
sourceTree = "<group>";
@@ -423,6 +426,7 @@
D1DEEC2922009E8000C525EE /* ModalRouter.swift in Sources */,
A6D5AF87221B131400DBF186 /* Image.swift in Sources */,
D1BFAF772215795900845EA0 /* Router.swift in Sources */,
A69E5891224BE4BE000874F8 /* TextViewExample.swift in Sources */,
A62AC65F22244A98009B3B25 /* Snake.swift in Sources */,
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
D1F7185D2215A4A1004E5951 /* LayerProps.swift in Sources */,
@@ -0,0 +1,71 @@
//
// TextViewExample.swift
// TokamakDemo-iOS
//
// Created by Matvii Hodovaniuk on 3/27/19.
// Copyright © 2019 Tokamak contributors. Tokamak is available under the
// Apache 2.0 license. See the LICENSE file for more info.
//
import Tokamak

struct TextViewExample: LeafComponent {
typealias Props = Null

static func render(props: Props, hooks: Hooks) -> AnyNode {
let alignment = hooks.state(0)

let cases = TextAlignment.allCases

return StackView.node(.init(
Edges.equal(to: .safeArea),
axis: .vertical,
distribution: .fill,
spacing: 10
), [
SegmentedControl.node(
.init(
value: alignment.value,
valueHandler: Handler { alignment.set($0) }
),
cases.map { String(describing: $0) }
),
TextView.node(.init(
textAlignment: cases[alignment.value],
value: exampleText
)),
])
}
}

let exampleText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus eros eros,\
id laoreet libero vehicula posuere. Etiam sit amet auctor mauris. In bibendum\
rhoncus tincidunt. Nulla facilisis sagittis tellus, vitae tempus elit interdum\
eget. Nam varius, velit ut condimentum suscipit, erat nunc molestie velit, a\
feugiat metus ante sit amet quam. Phasellus lobortis risus ac urna sagittis\
congue. Mauris vel eros lacus. Praesent sed feugiat felis, at ullamcorper\
massa. Integer vitae iaculis turpis, in dapibus nunc. Fusce maximus fermentum\
eros sed pellentesque. Mauris sed diam sed justo elementum iaculis non dignissi
erat.\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus eros eros,\
id laoreet libero vehicula posuere. Etiam sit amet auctor mauris. In bibendum\
rhoncus tincidunt. Nulla facilisis sagittis tellus, vitae tempus elit interdum\
eget. Nam varius, velit ut condimentum suscipit, erat nunc molestie velit, a\
feugiat metus ante sit amet quam. Phasellus lobortis risus ac urna sagittis\
congue. Mauris vel eros lacus. Praesent sed feugiat felis, at ullamcorper\
massa. Integer vitae iaculis turpis, in dapibus nunc. Fusce maximus fermentum\
eros sed pellentesque. Mauris sed diam sed justo elementum iaculis non dignissi
erat.\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus eros eros,\
id laoreet libero vehicula posuere. Etiam sit amet auctor mauris. In bibendum\
rhoncus tincidunt. Nulla facilisis sagittis tellus, vitae tempus elit interdum\
eget. Nam varius, velit ut condimentum suscipit, erat nunc molestie velit, a\
feugiat metus ante sit amet quam. Phasellus lobortis risus ac urna sagittis\
congue. Mauris vel eros lacus. Praesent sed feugiat felis, at ullamcorper\
massa. Integer vitae iaculis turpis, in dapibus nunc. Fusce maximus fermentum\
eros sed pellentesque. Mauris sed diam sed justo elementum iaculis non dignissi\
erat.
"""
@@ -27,6 +27,7 @@ enum AppRoute: String, CaseIterable {
case collection = "Collection View"
case tab = "Tab Example"
case throbber
case textView = "Text View"
}

extension AppRoute: CustomStringConvertible {
@@ -82,6 +83,8 @@ struct Router: NavigationRouter {
result = TabExample.node()
case .throbber:
result = ThrobberExample.node()
case .textView:
result = TextViewExample.node()
}

return NavigationItem.node(
@@ -0,0 +1,41 @@
//
// TextView.swift
// Tokamak
//
// Created by Matvii Hodovaniuk on 3/27/19.
//
public struct TextView: HostComponent {
public struct Props: Equatable, StyleProps, ValueControlProps {
public let style: Style?
public let allowsEditingTextAttributes: Bool
public let isEditable: Bool
public let textAlignment: TextAlignment
public let textColor: Color?
public let scrollOptions: ScrollOptions?
public let value: String
public let valueHandler: Handler<String>?

public init(
_ style: Style? = nil,
allowsEditingTextAttributes: Bool = false,
isEditable: Bool = true,
scrollOptions: ScrollOptions? = nil,
textAlignment: TextAlignment = .natural,
textColor: Color? = nil,
value: String = "",
valueHandler: Handler<String>? = nil
) {
self.style = style
self.allowsEditingTextAttributes = allowsEditingTextAttributes
self.isEditable = isEditable
self.textAlignment = textAlignment
self.textColor = textColor
self.scrollOptions = scrollOptions
self.value = value
self.valueHandler = valueHandler
}
}

public typealias Children = Null
}
@@ -5,7 +5,7 @@
// Created by Max Desiatov on 30/12/2018.
//
public enum TextAlignment {
public enum TextAlignment: CaseIterable {
case left
case right
case center
@@ -8,7 +8,7 @@
typealias Finalizer = (() -> ())?
typealias Effect = () -> Finalizer

protocol HookedComponent: class {
protocol HookedComponent: AnyObject {
/// State cells of this component indexed by order of `hooks.state` calls
var state: [UnsafeMutableRawPointer] { get set }

@@ -11,7 +11,7 @@
protocol are used by a reconciler (`StackReconciler` instance) to notify
the renderer about updates in the component tree.
*/
public protocol Renderer: class {
public protocol Renderer: AnyObject {
typealias Mounted = MountedComponent<Self>
typealias MountedHost = MountedHostComponent<Self>

@@ -0,0 +1,41 @@
//
// TextViewBox.swift
// TokamakUIKit
//
// Created by Matvii Hodovaniuk on 3/28/19.
//
import Tokamak
import UIKit

private final class Delegate<T: UITextView>:
NSObject,
UITextViewDelegate {
private let valueHandler: Handler<String>?

func textViewDidChange(_ textView: UITextView) {
valueHandler?.value(textView.text)
}

init(_ props: TextView.Props) {
valueHandler = props.valueHandler
}
}

final class TextViewBox: ViewBox<TokamakTextView> {
// this delegate stays as a constant and doesn't create a reference cycle
// swiftlint:disable:next weak_delegate
private let delegate: Delegate<UITextView>

init(
_ view: TokamakTextView,
_ viewController: UIViewController,
_ component: UIKitRenderer.MountedHost,
_ props: TextView.Props,
_ renderer: UIKitRenderer
) {
delegate = Delegate(props)
view.delegate = delegate
super.init(view, viewController, component.node)
}
}
@@ -8,7 +8,7 @@
import Tokamak
import UIKit

protocol ValueStorage: class {
protocol ValueStorage: AnyObject {
associatedtype Value

var value: Value { get set }
@@ -0,0 +1,46 @@
//
// TextView.swift
// TokamakUIKit
//
// Created by Matvii Hodovaniuk on 3/27/19.
//
import Tokamak
import UIKit

final class TokamakTextView: UITextView, Default {
static var defaultValue: TokamakTextView {
return TokamakTextView()
}
}

extension TextView: UIViewComponent {
public typealias RefTarget = UITextView

static func box(
for view: TokamakTextView,
_ viewController: UIViewController,
_ component: UIKitRenderer.MountedHost,
_ renderer: UIKitRenderer
) -> ViewBox<TokamakTextView> {
guard let props = component.node.props.value as? Props else {
fatalError("incorrect props type stored in ListView node")
}

return TextViewBox(view, viewController, component, props, renderer)
}

static func update(view box: ViewBox<TokamakTextView>,
_ props: TextView.Props,
_ children: Null) {
let view = box.view
if let scrollOptions = props.scrollOptions {
applyScrollOptions(box, scrollOptions)
}
view.allowsEditingTextAttributes = props.allowsEditingTextAttributes
view.isEditable = props.isEditable
view.textAlignment = NSTextAlignment(props.textAlignment)
view.textColor = props.textColor.flatMap { UIColor($0) }
view.text = props.value
}
}
@@ -21,12 +21,15 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
A629DA9F224D1CBB00FC14D4 /* TextViewBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A629DA9E224D1CBB00FC14D4 /* TextViewBox.swift */; };
A675A59922390218005173E5 /* TabPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59822390218005173E5 /* TabPresenter.swift */; };
A675A59B223910E1005173E5 /* TabPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59A223910E1005173E5 /* TabPresenter.swift */; };
A675A59D223AE721005173E5 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59C223AE721005173E5 /* TabItem.swift */; };
A675A59F223C2EDE005173E5 /* TabBarControllerBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59E223C2EDE005173E5 /* TabBarControllerBox.swift */; };
A69E5887224B8C60000874F8 /* Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E5886224B8C60000874F8 /* Scroll.swift */; };
A69E588B224BBF6B000874F8 /* ScrollOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E588A224BBF6B000874F8 /* ScrollOptions.swift */; };
A69E588D224BE3DC000874F8 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E588C224BE3DC000874F8 /* TextView.swift */; };
A69E588F224BE3F4000874F8 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E588E224BE3F4000874F8 /* TextView.swift */; };
D15E04332235829000BB0571 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15E04322235829000BB0571 /* Rectangle.swift */; };
D15E0447223582FD00BB0571 /* FirstBaseline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15E0435223582EE00BB0571 /* FirstBaseline.swift */; };
D15E0448223582FD00BB0571 /* CenterY.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15E0436223582EE00BB0571 /* CenterY.swift */; };
@@ -251,12 +254,15 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
A629DA9E224D1CBB00FC14D4 /* TextViewBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewBox.swift; sourceTree = "<group>"; };
A675A59822390218005173E5 /* TabPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPresenter.swift; sourceTree = "<group>"; };
A675A59A223910E1005173E5 /* TabPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPresenter.swift; sourceTree = "<group>"; };
A675A59C223AE721005173E5 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = "<group>"; };
A675A59E223C2EDE005173E5 /* TabBarControllerBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarControllerBox.swift; sourceTree = "<group>"; };
A69E5886224B8C60000874F8 /* Scroll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroll.swift; sourceTree = "<group>"; };
A69E588A224BBF6B000874F8 /* ScrollOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollOptions.swift; sourceTree = "<group>"; };
A69E588C224BE3DC000874F8 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
A69E588E224BE3F4000874F8 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
D15E042D223578BC00BB0571 /* TokamakUIKit.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TokamakUIKit.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
D15E042E223578BC00BB0571 /* Tokamak.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Tokamak.podspec; sourceTree = "<group>"; };
D15E042F223578BC00BB0571 /* TokamakAppKit.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TokamakAppKit.podspec; sourceTree = "<group>"; };
@@ -581,6 +587,7 @@
OBJ_29 /* TextField.swift */,
OBJ_30 /* View.swift */,
D1B90571224259FA0077CD5E /* Throbber.swift */,
A69E588C224BE3DC000874F8 /* TextView.swift */,
);
path = Host;
sourceTree = "<group>";
@@ -862,6 +869,7 @@
OBJ_89 /* ViewBox.swift */,
OBJ_90 /* ViewControllerBox.swift */,
A675A59E223C2EDE005173E5 /* TabBarControllerBox.swift */,
A629DA9E224D1CBB00FC14D4 /* TextViewBox.swift */,
);
path = Boxes;
sourceTree = "<group>";
@@ -899,6 +907,7 @@
A675A59A223910E1005173E5 /* TabPresenter.swift */,
A675A59C223AE721005173E5 /* TabItem.swift */,
D1B9057322425CED0077CD5E /* Throbber.swift */,
A69E588E224BE3F4000874F8 /* TextView.swift */,
);
path = Host;
sourceTree = "<group>";
@@ -1118,6 +1127,7 @@
OBJ_248 /* Ref.swift in Sources */,
OBJ_249 /* State.swift in Sources */,
OBJ_250 /* MountedComponent.swift in Sources */,
A69E588D224BE3DC000874F8 /* TextView.swift in Sources */,
OBJ_251 /* MountedCompositeComponent.swift in Sources */,
OBJ_252 /* MountedHostComponent.swift in Sources */,
OBJ_253 /* MountedNull.swift in Sources */,
@@ -1216,6 +1226,7 @@
OBJ_336 /* ControlBox.swift in Sources */,
OBJ_337 /* TableViewBox.swift in Sources */,
OBJ_338 /* ValueControlBox.swift in Sources */,
A69E588F224BE3F4000874F8 /* TextView.swift in Sources */,
OBJ_339 /* ViewBox.swift in Sources */,
OBJ_340 /* ViewControllerBox.swift in Sources */,
OBJ_341 /* Button.swift in Sources */,
@@ -1254,6 +1265,7 @@
A675A59D223AE721005173E5 /* TabItem.swift in Sources */,
A69E5887224B8C60000874F8 /* Scroll.swift in Sources */,
OBJ_371 /* Right.swift in Sources */,
A629DA9F224D1CBB00FC14D4 /* TextViewBox.swift in Sources */,
OBJ_372 /* Top.swift in Sources */,
OBJ_373 /* Trailing.swift in Sources */,
OBJ_374 /* Width.swift in Sources */,

0 comments on commit 822d527

Please sign in to comment.
You can’t perform that action at this time.