Add ScrollView (#58)
* Init ScrollView * Add ScrollView component * Update ScollView * Update Scroll extension * Fix Ref index * Add scroll example with ref styling * Init zoom * Add contentInset, bounces, scrollsToTop to ScrollView * Add alwaysBounceVertical to ScrollView * Add alwaysBounceHorizontal, indicatorStyle to ScrollView * Add scrollIndicatorInsets, showsHorizontalScrollIndicator, showsVerticalScrollIndicator to ScrollView * Add isDirectionalLockEnabled, isPagingEnabled, isScrollEnabled to ScrollView * Add maximumZoomScale, minimumZoomScale, zoomScale to ScrollView * Add bouncesZoom to ScrollView * Add extension to RefComponent * Add zoom to Image example * Add simple ScrollView example * Format code
This commit is contained in:
parent
a1b5d0388a
commit
f7b5384c95
|
@ -22,6 +22,7 @@
|
|||
A67717202226DC7C0028A6F3 /* Gameboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A677171F2226DC7C0028A6F3 /* Gameboard.swift */; };
|
||||
A67717222226E7FD0028A6F3 /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67717212226E7FD0028A6F3 /* Menu.swift */; };
|
||||
A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; };
|
||||
A6FEF7952227C1CC008BB292 /* Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FEF7942227C1CC008BB292 /* Scroll.swift */; };
|
||||
C449B806DFEE55B6CEE6478C /* libPods-TokamakDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B96B435A9D67621D318616E /* libPods-TokamakDemo.a */; };
|
||||
D11DB6432219C03000013FC3 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11DB6422219C03000013FC3 /* Timer.swift */; };
|
||||
D1BB3D302223F6B400C30062 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BB3D2F2223F6B400C30062 /* Animation.swift */; };
|
||||
|
@ -59,6 +60,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>"; };
|
||||
A6D5AF86221B131400DBF186 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
A6FEF7942227C1CC008BB292 /* Scroll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroll.swift; sourceTree = "<group>"; };
|
||||
A9EEF813955DAEEFE1D52ED4 /* Pods-TokamakDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokamakDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokamakDemo/Pods-TokamakDemo.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
C6DA99382B6892EAB361742F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||
D11DB6422219C03000013FC3 /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
|
||||
|
@ -200,6 +202,7 @@
|
|||
A6D5AF86221B131400DBF186 /* Image.swift */,
|
||||
A62AC6532223F5CD009B3B25 /* TextField.swift */,
|
||||
D1BB3D2F2223F6B400C30062 /* Animation.swift */,
|
||||
A6FEF7942227C1CC008BB292 /* Scroll.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
|
@ -315,6 +318,7 @@
|
|||
D1F7185F2215A5D0004E5951 /* Constraints.swift in Sources */,
|
||||
A62AC65922243DCB009B3B25 /* Cell.swift in Sources */,
|
||||
D11DB6432219C03000013FC3 /* Timer.swift in Sources */,
|
||||
A6FEF7952227C1CC008BB292 /* Scroll.swift in Sources */,
|
||||
A62AC65622243CC3009B3B25 /* SnakeGame.swift in Sources */,
|
||||
D1F7185322159E09004E5951 /* Controls.swift in Sources */,
|
||||
D1DEEC2922009E8000C525EE /* ModalRouter.swift in Sources */,
|
||||
|
|
|
@ -8,20 +8,48 @@
|
|||
|
||||
import Tokamak
|
||||
|
||||
struct ImageExample: PureLeafComponent {
|
||||
class ScrollDelegate: NSObject, UIScrollViewDelegate {
|
||||
var view: UIView?
|
||||
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageExample: LeafComponent {
|
||||
typealias Props = Null
|
||||
|
||||
static func render(props _: Null) -> AnyNode {
|
||||
static func render(props _: Null, hooks: Hooks) -> AnyNode {
|
||||
let refScroll = hooks.ref(type: UIScrollView.self)
|
||||
let refImage = hooks.ref(type: UIImageView.self)
|
||||
let delegate = hooks.ref(ScrollDelegate())
|
||||
|
||||
hooks.effect {
|
||||
guard let image = refImage.value else { return }
|
||||
guard let scroll = refScroll.value else { return }
|
||||
|
||||
delegate.value.view = image
|
||||
scroll.delegate = delegate.value
|
||||
}
|
||||
|
||||
return StackView.node(.init(
|
||||
Edges.equal(to: .safeArea),
|
||||
alignment: .center,
|
||||
axis: .vertical,
|
||||
distribution: .fillEqually
|
||||
distribution: .fill
|
||||
), [
|
||||
ImageView.node(.init(
|
||||
Style(contentMode: .scaleAspectFit),
|
||||
image: Image(name: "tokamak")
|
||||
)),
|
||||
Label.node("You can pan and zoom this image"),
|
||||
ScrollView.node(
|
||||
.init(
|
||||
Style(Width.equal(to: .parent)),
|
||||
maximumZoomScale: 2.0
|
||||
),
|
||||
ImageView.node(.init(
|
||||
Style(Edges.equal(to: .parent), contentMode: .scaleAspectFit),
|
||||
image: Image(name: "tokamak")
|
||||
), ref: refImage),
|
||||
ref: refScroll
|
||||
),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Scroll.swift
|
||||
// TokamakDemo
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/28/19.
|
||||
// Copyright © 2019 Tokamak. All rights reserved.
|
||||
//
|
||||
|
||||
import Tokamak
|
||||
|
||||
struct ScrollViewExample: LeafComponent {
|
||||
typealias Props = Null
|
||||
|
||||
static func render(props: Props, hooks: Hooks) -> AnyNode {
|
||||
return View.node(
|
||||
.init(Style(Edges.equal(to: .safeArea))),
|
||||
ScrollView.node(
|
||||
.init(Style(Edges.equal(to: .parent))),
|
||||
StackView.node(
|
||||
.init(
|
||||
Edges.equal(to: .parent),
|
||||
axis: .vertical,
|
||||
distribution: .fill
|
||||
),
|
||||
(1..<100).map { Label.node("Text \($0)") }
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ enum AppRoute: String, CaseIterable {
|
|||
case textField = "Text Field"
|
||||
case animation
|
||||
case snakeGame = "Snake Game"
|
||||
case scrollView = "Scroll"
|
||||
}
|
||||
|
||||
extension AppRoute: CustomStringConvertible {
|
||||
|
@ -68,6 +69,8 @@ struct Router: NavigationRouter {
|
|||
result = Animation.node()
|
||||
case .snakeGame:
|
||||
result = SnakeGame.node()
|
||||
case .scrollView:
|
||||
result = ScrollViewExample.node()
|
||||
}
|
||||
|
||||
return NavigationItem.node(
|
||||
|
|
|
@ -154,3 +154,12 @@ extension RefComponent where Children == Null {
|
|||
return node(props, Null(), ref: ref)
|
||||
}
|
||||
}
|
||||
|
||||
extension RefComponent where Props: Default, Props.DefaultValue == Props {
|
||||
public static func node(
|
||||
_ children: Children,
|
||||
ref: Ref<RefTarget?>
|
||||
) -> AnyNode {
|
||||
return node(Props.defaultValue, children, ref: ref)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// ScrollView.swift
|
||||
// Tokamak
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/28/19.
|
||||
//
|
||||
|
||||
public struct ScrollView: HostComponent {
|
||||
public struct Props: Equatable, StyleProps, Default {
|
||||
public static var defaultValue: Props {
|
||||
return Props()
|
||||
}
|
||||
|
||||
public struct EdgeInsets: Equatable {
|
||||
public let bottom: Float
|
||||
public let left: Float
|
||||
public let right: Float
|
||||
public let top: Float
|
||||
|
||||
public init(
|
||||
top: Float = 0,
|
||||
left: Float = 0,
|
||||
bottom: Float = 0,
|
||||
right: Float = 0
|
||||
) {
|
||||
self.top = top
|
||||
self.left = left
|
||||
self.bottom = bottom
|
||||
self.right = right
|
||||
}
|
||||
}
|
||||
|
||||
public enum IndicatorStyle {
|
||||
case `default`
|
||||
case black
|
||||
case white
|
||||
}
|
||||
|
||||
public let style: Style?
|
||||
public let alwaysBounceHorizontal: Bool
|
||||
public let alwaysBounceVertical: Bool
|
||||
public let bounces: Bool
|
||||
public let bouncesZoom: Bool
|
||||
public let contentInset: EdgeInsets
|
||||
public let indicatorStyle: IndicatorStyle
|
||||
public let isDirectionalLockEnabled: Bool
|
||||
public let isPagingEnabled: Bool
|
||||
public let isScrollEnabled: Bool
|
||||
public let maximumZoomScale: Float
|
||||
public let minimumZoomScale: Float
|
||||
public let scrollIndicatorInsets: EdgeInsets
|
||||
public let scrollsToTop: Bool
|
||||
public let showsHorizontalScrollIndicator: Bool
|
||||
public let showsVerticalScrollIndicator: Bool
|
||||
public let zoomScale: Float
|
||||
|
||||
public init(
|
||||
_ style: Style? = nil,
|
||||
alwaysBounceHorizontal: Bool = false,
|
||||
alwaysBounceVertical: Bool = false,
|
||||
bounces: Bool = true,
|
||||
bouncesZoom: Bool = true,
|
||||
contentInset: EdgeInsets = EdgeInsets(),
|
||||
indicatorStyle: IndicatorStyle = .default,
|
||||
isDirectionalLockEnabled: Bool = false,
|
||||
isPagingEnabled: Bool = false,
|
||||
isScrollEnabled: Bool = true,
|
||||
maximumZoomScale: Float = 1.0,
|
||||
minimumZoomScale: Float = 1.0,
|
||||
scrollIndicatorInsets: EdgeInsets = EdgeInsets(),
|
||||
scrollsToTop: Bool = true,
|
||||
showsHorizontalScrollIndicator: Bool = true,
|
||||
showsVerticalScrollIndicator: Bool = true,
|
||||
zoomScale: Float = 1.0
|
||||
) {
|
||||
self.style = style
|
||||
self.alwaysBounceHorizontal = alwaysBounceHorizontal
|
||||
self.alwaysBounceVertical = alwaysBounceVertical
|
||||
self.bounces = bounces
|
||||
self.bouncesZoom = bouncesZoom
|
||||
self.contentInset = contentInset
|
||||
self.isDirectionalLockEnabled = isDirectionalLockEnabled
|
||||
self.isPagingEnabled = isPagingEnabled
|
||||
self.indicatorStyle = indicatorStyle
|
||||
self.isScrollEnabled = isScrollEnabled
|
||||
self.maximumZoomScale = maximumZoomScale
|
||||
self.minimumZoomScale = minimumZoomScale
|
||||
self.scrollIndicatorInsets = scrollIndicatorInsets
|
||||
self.scrollsToTop = scrollsToTop
|
||||
self.showsVerticalScrollIndicator = showsVerticalScrollIndicator
|
||||
self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator
|
||||
self.zoomScale = zoomScale
|
||||
}
|
||||
}
|
||||
|
||||
public typealias Children = AnyNode
|
||||
}
|
|
@ -109,7 +109,7 @@ public final class Hooks {
|
|||
(initialized from `initial` if current was absent)
|
||||
*/
|
||||
func ref<T>(_ initial: Ref<T>) -> Ref<T> {
|
||||
defer { stateIndex += 1 }
|
||||
defer { refIndex += 1 }
|
||||
|
||||
guard let component = component else {
|
||||
assertionFailure("hooks.state should only be called within `render`")
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// ScrollView.swift
|
||||
// TokamakUIKit
|
||||
//
|
||||
// Created by Matvii Hodovaniuk on 2/28/19.
|
||||
//
|
||||
|
||||
import Tokamak
|
||||
import UIKit
|
||||
|
||||
final class TokamakScrollView: UIScrollView, Default {
|
||||
static var defaultValue: TokamakScrollView {
|
||||
return TokamakScrollView()
|
||||
}
|
||||
}
|
||||
|
||||
extension UIEdgeInsets {
|
||||
public init(_ edges: ScrollView.Props.EdgeInsets) {
|
||||
self.init(
|
||||
top: CGFloat(edges.top),
|
||||
left: CGFloat(edges.left),
|
||||
bottom: CGFloat(edges.bottom),
|
||||
right: CGFloat(edges.right)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIScrollView.IndicatorStyle {
|
||||
public init(_ type: ScrollView.Props.IndicatorStyle) {
|
||||
switch type {
|
||||
case .default:
|
||||
self = .default
|
||||
case .black:
|
||||
self = .black
|
||||
case .white:
|
||||
self = .white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ScrollView: UIViewComponent {
|
||||
public typealias RefTarget = UIScrollView
|
||||
|
||||
static func update(
|
||||
view box: ViewBox<TokamakScrollView>,
|
||||
_ props: ScrollView.Props,
|
||||
_ children: AnyNode
|
||||
) {
|
||||
let view = box.view
|
||||
view.alwaysBounceHorizontal = props.alwaysBounceHorizontal
|
||||
view.alwaysBounceVertical = props.alwaysBounceVertical
|
||||
view.bounces = props.bounces
|
||||
view.bouncesZoom = props.bouncesZoom
|
||||
view.contentInset = UIEdgeInsets(props.contentInset)
|
||||
view.isDirectionalLockEnabled = props.isDirectionalLockEnabled
|
||||
view.isPagingEnabled = props.isPagingEnabled
|
||||
view.indicatorStyle = UIScrollView.IndicatorStyle(props.indicatorStyle)
|
||||
view.isScrollEnabled = props.isScrollEnabled
|
||||
view.maximumZoomScale = CGFloat(props.maximumZoomScale)
|
||||
view.minimumZoomScale = CGFloat(props.minimumZoomScale)
|
||||
view.scrollIndicatorInsets = UIEdgeInsets(props.scrollIndicatorInsets)
|
||||
view.scrollsToTop = props.scrollsToTop
|
||||
view.showsVerticalScrollIndicator = props.showsVerticalScrollIndicator
|
||||
view.showsHorizontalScrollIndicator = props.showsHorizontalScrollIndicator
|
||||
view.zoomScale = CGFloat(props.zoomScale)
|
||||
}
|
||||
}
|
|
@ -120,6 +120,8 @@ extension UIViewComponent where Target == Target.DefaultValue,
|
|||
box.view.addArrangedSubview(target)
|
||||
// no covariance/contravariance in Swift generics require next
|
||||
// two cases to be duplicated :(
|
||||
case let box as ViewBox<TokamakScrollView>:
|
||||
box.view.addSubview(target)
|
||||
case let box as ViewBox<UIView>:
|
||||
box.view.addSubview(target)
|
||||
case let box as ViewBox<TokamakView>:
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
A6AB60B5221C70340063F88A /* ContentMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6AB60B4221C70340063F88A /* ContentMode.swift */; };
|
||||
A6D188EC221F1CA300C3947C /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D188EB221F1CA300C3947C /* Accessibility.swift */; };
|
||||
A6D188EE2220221B00C3947C /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D188ED2220221B00C3947C /* TextField.swift */; };
|
||||
A6FEF7912227BAB5008BB292 /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FEF7902227BAB5008BB292 /* ScrollView.swift */; };
|
||||
A6FEF7932227BAD0008BB292 /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FEF7922227BAD0008BB292 /* ScrollView.swift */; };
|
||||
D1B4F8DB221AFB0E00C53C42 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4F8D9221AFB0800C53C42 /* ImageView.swift */; };
|
||||
D1B4F8DD221AFB2B00C53C42 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4F8DC221AFB2B00C53C42 /* ImageView.swift */; };
|
||||
D1CFC81D222546F500B03222 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CFC81C222546F500B03222 /* Image.swift */; };
|
||||
|
@ -192,6 +194,8 @@
|
|||
A6D188EB221F1CA300C3947C /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
|
||||
A6D188ED2220221B00C3947C /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
|
||||
A6D188EF2220222F00C3947C /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
|
||||
A6FEF7902227BAB5008BB292 /* ScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = "<group>"; };
|
||||
A6FEF7922227BAD0008BB292 /* ScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = "<group>"; };
|
||||
D1B4F8D9221AFB0800C53C42 /* ImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||
D1B4F8DC221AFB2B00C53C42 /* ImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||
D1CFC81C222546F500B03222 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
|
@ -437,6 +441,7 @@
|
|||
OBJ_29 /* Switch.swift */,
|
||||
OBJ_30 /* View.swift */,
|
||||
A6D188EF2220222F00C3947C /* TextField.swift */,
|
||||
A6FEF7922227BAD0008BB292 /* ScrollView.swift */,
|
||||
);
|
||||
path = Host;
|
||||
sourceTree = "<group>";
|
||||
|
@ -575,6 +580,7 @@
|
|||
OBJ_82 /* Switch.swift */,
|
||||
A6D188ED2220221B00C3947C /* TextField.swift */,
|
||||
OBJ_83 /* View.swift */,
|
||||
A6FEF7902227BAB5008BB292 /* ScrollView.swift */,
|
||||
);
|
||||
path = Host;
|
||||
sourceTree = "<group>";
|
||||
|
@ -811,6 +817,7 @@
|
|||
OBJ_179 /* CenterY.swift in Sources */,
|
||||
OBJ_180 /* Constraint.swift in Sources */,
|
||||
OBJ_181 /* Edges.swift in Sources */,
|
||||
A6FEF7912227BAB5008BB292 /* ScrollView.swift in Sources */,
|
||||
OBJ_182 /* FirstBaseline.swift in Sources */,
|
||||
OBJ_183 /* Height.swift in Sources */,
|
||||
OBJ_184 /* LastBaseline.swift in Sources */,
|
||||
|
@ -910,6 +917,7 @@
|
|||
OBJ_281 /* Height.swift in Sources */,
|
||||
OBJ_282 /* LastBaseline.swift in Sources */,
|
||||
OBJ_283 /* Leading.swift in Sources */,
|
||||
A6FEF7932227BAD0008BB292 /* ScrollView.swift in Sources */,
|
||||
OBJ_284 /* Left.swift in Sources */,
|
||||
OBJ_285 /* OwnConstraint.swift in Sources */,
|
||||
OBJ_286 /* Right.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue