Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LiveView improvement #51

Merged
merged 11 commits into from
Apr 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions Playgrounds/NumswPlayground.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,33 @@ import CoreGraphics
#endif

public class NumswPlayground {

internal init() {
#if os(iOS)
viewController = RenderTableViewController()
viewController = RenderTableViewController(state: viewState)
#endif
}

#if os(iOS)
public let viewController: RenderTableViewController
internal let viewState = RenderTableViewController.State()
#endif

public func append(renderer: ChartRenderer) {
renderers.append(renderer)
public func append(renderer: Renderer) {
#if os(iOS)
viewController.append(renderer: renderer)
#endif
}

#if os(iOS)
public func print(_ matrix: Matrix<Double>) {
let matrixTextRenderer = MatrixTextRenderer(matrix)
renderers.append(matrixTextRenderer)
append(renderer: matrixTextRenderer)
}

public func print(_ string: String) {
let textRenderer = TextRenderer(string)
renderers.append(textRenderer)
append(renderer: textRenderer)
}
#endif

Expand Down Expand Up @@ -85,13 +89,14 @@ public class NumswPlayground {
return _shared!
}

private var renderers: [Renderer] = [] {
//private var renderers: [Renderer] = []
/*private var renderers: [ChartRenderer] = [] {
didSet {
#if os(iOS)
viewController.renderers = renderers.map { $0 as Renderer }
viewController.replace(renderers: renderers.map { $0 as Renderer })
#endif
}
}
}*/

private var chartBuilder: ChartBuilder?

Expand Down
68 changes: 68 additions & 0 deletions Playgrounds/PlaygroundKeyValueStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// PlaygroundKeyValueStore.swift
// sandbox
//
// Created by Yusuke Ito on 4/16/17.
// Copyright © 2017 sonson. All rights reserved.
//


// PlaygroundKeyValueStore as UserDefautls for sandbox app

#if os(iOS)
import Foundation


#if SANDBOX_APP
// sandbox app
internal enum PlaygroundValue {
case array([PlaygroundValue])
case boolean(Bool)
case data(Data)
case date(Date)
case dictionary([String: PlaygroundValue])
case floatingPoint(Double)
case integer(Int)
case string(String)
}
internal class PlaygroundKeyValueStore {
static let current = PlaygroundKeyValueStore()
private init() {

}
subscript(key: String) -> PlaygroundValue? {
set {
print("KeyValueStore setting \(newValue as Any) for \(key)")
if let value = newValue {
switch value {
case .floatingPoint(let double):
UserDefaults.standard.set(double, forKey: key)
case .integer(let integer):
UserDefaults.standard.set(integer, forKey: key)
default:
fatalError("Not implemented")
}
} else {
UserDefaults.standard.removeObject(forKey: key)
}
}
get {
print("KeyValueStore getting for \(key)")
guard let object = UserDefaults.standard.object(forKey: key) else {
return nil
}
if let integer = object as? Int {
return .integer(integer)
} else if let double = object as? Double {
return .floatingPoint(double)
} else {
fatalError("Not implemented")
}
}
}
}
#else
// playground
#endif

#endif
30 changes: 25 additions & 5 deletions Playgrounds/RenderTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import UIKit

internal class RenderTableViewCell: UITableViewCell {

private static let renderQueue = DispatchQueue(label: "Renderer-Queue")

var renderer: Renderer? {
willSet {
Expand All @@ -22,7 +24,7 @@ internal class RenderTableViewCell: UITableViewCell {

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
print("RenderTableViewCell init")
//print("RenderTableViewCell init")
self.separatorInset = .zero
self.selectionStyle = .none
renderImageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Expand All @@ -36,14 +38,18 @@ internal class RenderTableViewCell: UITableViewCell {
}

deinit {
print("RenderTableViewCell deinit")
//print("RenderTableViewCell deinit")
}

private var renderedImageSize = CGSize.zero

private var isImageViewDirty: Bool {
return renderedImageSize != self.contentView.bounds.size
}

func updateImageViewIfNeeded() {
print("rendering bounds: \(self.contentView.bounds)")
if renderedImageSize == self.contentView.bounds.size &&
if isImageViewDirty == false &&
self.renderImageView.image != nil {
// already rendered
return
Expand All @@ -53,9 +59,23 @@ internal class RenderTableViewCell: UITableViewCell {

private func updateImageView() {
guard let renderer = self.renderer else { return }
let image = renderer.renderToImage(size: self.contentView.bounds.size)
self.renderImageView.image = image
let withFadeAnimation = isImageViewDirty && self.renderImageView.image != nil

renderedImageSize = self.contentView.bounds.size
type(of: self).renderQueue.async {
let image = renderer.renderToImage(size: self.contentView.bounds.size)
DispatchQueue.main.async {
self.renderImageView.image = image

if withFadeAnimation {
let transition = CATransition()
transition.duration = 0.25
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.renderImageView.layer.add(transition, forKey: nil)
}
}
}
}
}
#endif
124 changes: 118 additions & 6 deletions Playgrounds/RenderTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@

#if os(iOS)
import UIKit
import QuartzCore

#if SANDBOX_APP
// sandbox app
#else
// playground
import PlaygroundSupport
#endif

public class RenderTableViewController: UITableViewController, UZTextViewDelegate {

var renderers: [Renderer] = [] {
private var renderers: [Renderer] = [] {
didSet {
for i in 0..<renderers.count {
renderers[i].parentViewSize = self.tableView.frame.size
Expand All @@ -22,7 +30,41 @@ public class RenderTableViewController: UITableViewController, UZTextViewDelegat
}
}

public init() {
internal class State {
private enum Key: String {
case tableViewScrollOffsetX = "table_view_scrolloffset_x"
case tableViewScrollOffsetY = "table_view_scrolloffset_y"
case rendererCount = "renderer_count"
}

var tableViewScrollOffset: CGPoint = .zero
init() {
let kvs = PlaygroundKeyValueStore.current
if let xValue = kvs[Key.tableViewScrollOffsetX.rawValue],
case .floatingPoint(let x) = xValue,
let yValue = kvs[Key.tableViewScrollOffsetY.rawValue],
case .floatingPoint(let y) = yValue {
tableViewScrollOffset = CGPoint(x: x, y: y)
} else {
tableViewScrollOffset = .zero
}
}

func reset() {
tableViewScrollOffset = .zero
}

func sync() {
let kvs = PlaygroundKeyValueStore.current
kvs[Key.tableViewScrollOffsetX.rawValue] = .floatingPoint(Double(tableViewScrollOffset.x))
kvs[Key.tableViewScrollOffsetY.rawValue] = .floatingPoint(Double(tableViewScrollOffset.y))
}
}

private var state: State

internal init(state: State) {
self.state = state
super.init(style: .plain)
}

Expand All @@ -41,20 +83,65 @@ public class RenderTableViewController: UITableViewController, UZTextViewDelegat
tableView.register(TextTableViewCell.self, forCellReuseIdentifier: "TextTableViewCell")
}

// not in use?
/*public func replace(renderers: [Renderer]) {
self.renderers = renderers
self.tableView.reloadData()
}*/

public func removeAllRenderers() {
self.renderers = []
self.tableView.reloadData()
}

public func append(renderer: Renderer) {
// partial update
self.renderers.append(renderer)
self.tableView.reloadData()
//self.tableView.insertRows(at: [IndexPath(row: renderers.count-1, section: 0)], with: .none)
tableView.reloadData()

// scroll to previous content position
let lastRowOffset = tableView.rectForRow(at: IndexPath(row: renderers.count-1, section: 0))

let viewBounds = UIEdgeInsetsInsetRect(view.bounds, tableView.contentInset)
let maxYOffset = max(lastRowOffset.origin.y + lastRowOffset.size.height - viewBounds.size.height, 0) // 0 ~
let offset = CGPoint(x: state.tableViewScrollOffset.x, y: min(maxYOffset, state.tableViewScrollOffset.y))
tableView.setContentOffset(offset, animated: false)
}

// some times not invoked?? on iPad playground
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// will ??
//self.tableView.reloadData()
updateVisibleCellImagesIfNeeded()
}

// some times not invoked?? on iPad playground
public override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.visibleCells.flatMap({ $0 as? RenderTableViewCell}).forEach({ $0.updateImageViewIfNeeded() })
updateVisibleCellImagesIfNeeded()
}

private func updateVisibleCellImagesIfNeeded() {
// on orientation changed
for view in tableView.visibleCells {
(view as? RenderTableViewCell)?.updateImageViewIfNeeded()
}
}

// some times not invoked?? on iPad playground
/*public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
updateVisibleCellImagesIfNeeded()

super.viewWillTransition(to: size, with: coordinator)
}*/

// TODO:
// `view*LayoutSubviews` seems to be not invoked on iPad playground
// temporary, we use this deprecated method until the solution is found
public override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
super.willAnimateRotation(to: toInterfaceOrientation, duration: duration)
state.reset()
updateVisibleCellImagesIfNeeded()
}

public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
Expand Down Expand Up @@ -98,5 +185,30 @@ public class RenderTableViewController: UITableViewController, UZTextViewDelegat
public func selectingStringEnded(_ textView: UZTextView) {
self.tableView.isScrollEnabled = true
}

#if SANDBOX_APP
// Tap any table view cell to dismiss
public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.navigationController?.popViewController(animated: true)
}
#endif

public override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
storeTableViewScrollOffset()
}

public override func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
storeTableViewScrollOffset()
}

public override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
storeTableViewScrollOffset()
}

private func storeTableViewScrollOffset() {
// store tableview scroll offset
state.tableViewScrollOffset = tableView.contentOffset
state.sync()
}
}
#endif
6 changes: 6 additions & 0 deletions Playgrounds/sandbox/sandbox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
148967C11E9786D900F19D91 /* TextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148967C01E9786D900F19D91 /* TextTableViewCell.swift */; };
148967C31E9786F400F19D91 /* RenderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148967C21E9786F400F19D91 /* RenderTableViewCell.swift */; };
148967C71E97921800F19D91 /* SelectTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148967C61E97921800F19D91 /* SelectTableViewController.swift */; };
52CCAD111EA3913500D6D6EB /* PlaygroundKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CCAD101EA3913500D6D6EB /* PlaygroundKeyValueStore.swift */; };
52CCAD121EA3913500D6D6EB /* PlaygroundKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CCAD101EA3913500D6D6EB /* PlaygroundKeyValueStore.swift */; };
D68EF7091E86C8C600927991 /* AxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A0B5BB1E6FE801004AE3B4 /* AxisRenderer.swift */; };
D68EF70A1E86C8C600927991 /* Chart.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A0B5BC1E6FE801004AE3B4 /* Chart.swift */; };
D68EF70B1E86C8C600927991 /* ChartBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A0B5BD1E6FE801004AE3B4 /* ChartBuilder.swift */; };
Expand Down Expand Up @@ -128,6 +130,7 @@
148967C01E9786D900F19D91 /* TextTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextTableViewCell.swift; path = ../TextTableViewCell.swift; sourceTree = "<group>"; };
148967C21E9786F400F19D91 /* RenderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RenderTableViewCell.swift; path = ../RenderTableViewCell.swift; sourceTree = "<group>"; };
148967C61E97921800F19D91 /* SelectTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectTableViewController.swift; sourceTree = "<group>"; };
52CCAD101EA3913500D6D6EB /* PlaygroundKeyValueStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaygroundKeyValueStore.swift; path = ../PlaygroundKeyValueStore.swift; sourceTree = "<group>"; };
52E1EE3E1E6A8A2E008D19BB /* sandboxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = sandboxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
52E1EE421E6A8A2E008D19BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D68EF6FD1E86C89600927991 /* NumswRenderer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NumswRenderer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -279,6 +282,7 @@
144A150B1E971A1400B42288 /* MatrixTextRenderer.swift */,
D6A0B5C41E6FE801004AE3B4 /* NumswPlayground_Playground.swift */,
D6A0B5C51E6FE801004AE3B4 /* NumswPlayground.swift */,
52CCAD101EA3913500D6D6EB /* PlaygroundKeyValueStore.swift */,
D6A0B5C61E6FE801004AE3B4 /* Renderer.swift */,
D6A0B5C91E6FE801004AE3B4 /* RendererUtil.swift */,
D6A0B5CA1E6FE801004AE3B4 /* RenderScrollViewController.swift */,
Expand Down Expand Up @@ -606,6 +610,7 @@
144A150C1E971A1400B42288 /* MatrixTextRenderer.swift in Sources */,
D68EF7101E86C8C600927991 /* LineGraph.swift in Sources */,
144A15101E971B9D00B42288 /* UZCursor.swift in Sources */,
52CCAD111EA3913500D6D6EB /* PlaygroundKeyValueStore.swift in Sources */,
D68EF7151E86C8C600927991 /* RendererUtil.swift in Sources */,
144A15131E971C0400B42288 /* UZLoupe.swift in Sources */,
144A15121E971B9D00B42288 /* UZTextView.swift in Sources */,
Expand All @@ -630,6 +635,7 @@
D68EF77B1E86DA4C00927991 /* ScatterGraphRenderer.swift in Sources */,
D68EF77C1E86DA4C00927991 /* ScatterGraph.swift in Sources */,
D68EF77D1E86DA4C00927991 /* Renderer.swift in Sources */,
52CCAD121EA3913500D6D6EB /* PlaygroundKeyValueStore.swift in Sources */,
D68EF77E1E86DA4C00927991 /* DummyData.swift in Sources */,
D68EF77F1E86DA4C00927991 /* RenderScrollViewController.swift in Sources */,
D68EF7801E86DA4C00927991 /* LineGraph.swift in Sources */,
Expand Down