Data transfer We can’t directly pass Dictionary to Native, because yogo will parse these large amounts of data, which will cause performance problems. We’d better serialize them into JSON strings and pass large amounts of data between JS and Native. We calculate all index values in the background thread and then update the UI on the main thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @objc func reloadOptionList (_ reactTag: NSNumber, optionList: String) { bridge.uiManager.addUIBlock { (uimanager, viewRegistry) in guard let viewRegistry = viewRegistry, let containerView = viewRegistry[reactTag] as? HTKLineContainerView else { return } type(of: self). queue. async { do { guard let optionList = try JSONSerialization.jsonObject(with: optionList.data(using: .utf8) ?? Data(), options: .allowFragments) as? [String: Any] else { return } containerView.configManager.reloadOptionList(optionList) DispatchQueue. main. async { containerView. reloadConfigManager(containerView.configManager) } } catch (_) { } } } }
Chart wrapper There are many kinds of charts and indicators that need to be drawn, so we encapsulate the same parts of them into protocols. Similarly, in swift, we can add default implementations to protocols, or add public methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protocol HTKLineDrawProtocol: class { func minMaxRange (_ visibleModelArray: [HTKLineModel], _ configManager: HTKLineConfigManager) -> Range<CGFloat> func drawCandle (_ model: HTKLineModel, _ index: Int, _ maxValue: CGFloat, _ minValue: CGFloat, _ baseY: CGFloat, _ height: CGFloat, _ context: CGContext, _ configManager: HTKLineConfigManager) func drawLine (_ model: HTKLineModel, _ lastModel: HTKLineModel, _ maxValue: CGFloat, _ minValue: CGFloat, _ baseY: CGFloat, _ height: CGFloat, _ index: Int, _ lastIndex: Int, _ context: CGContext, _ configManager : HTKLineConfigManager) func drawText (_ model: HTKLineModel, _ baseX: CGFloat, _ baseY: CGFloat, _ context: CGContext, _ configManager: HTKLineConfigManager) func drawValue (_ maxValue: CGFloat, _ minValue: CGFloat, _ baseX: CGFloat, _ baseY: CGFloat, _ height: CGFloat, _ context: CGContext, _ configManager: HTKLineConfigManager) }
Chart drawing Drawing processing We need a common point conversion method. We first draw the candles of the main chart and the sub-chart, and then draw the text, ruler, maximum and minimum, time scale, current price horizontal line, long-pressed detail panel, etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 override func draw (_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext(), configManager.modelArray.count > 0 else { return } calculateBaseHeight() contextTranslate(context, CGFloat(visibleRange.lowerBound) * configManager.itemWidth, { context in drawCandle(context) }) contextTranslate(context, contentOffset.x, { context in drawText(context) drawValue(context) drawHighLow(context) drawTime(context) drawClosePrice(context) drawSelectedLine(context) drawSelectedBoard(context) drawSelectedTime(context) drawContext. draw(contentOffset. x) }) }
When the ScrollView scrolls horizontally, in order to save performance, we only draw in the visible range of the screen, and then calculate the accurate offset for drawing
1 2 3 4 5 6 7 8 9 10 func scrollViewDidScroll (_ scrollView: UIScrollView) { let contentOffsetX = scrollView. contentOffset. x var visibleStartIndex = Int(floor(contentOffsetX / configManager. itemWidth)) var visibleEndIndex = Int(ceil((contentOffsetX + scrollView. bounds. size. width) / configManager. itemWidth)) visibleStartIndex = min(max(0 , visibleStartIndex), configManager.modelArray.count - 1 ) visibleEndIndex = min(max(0 , visibleEndIndex), configManager.modelArray.count - 1 ) visibleRange = visibleStartIndex...visibleEndIndex self. setNeedsDisplay() }
Zoom gesture handling When the ScrollView pinch zoom gesture, we need to recalculate the offset for drawing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @objc func pinchSelector (_ gesture: UIPinchGestureRecognizer) { switch gesture. state { case .changed: scale += (gesture. scale - 1 ) / 10 default : break } scale = max(0.3 , min(scale, 3 )) let width = bounds. size. width let halfWidth = width / 2 let offsetScale = (contentOffset.x + halfWidth) / (contentSize.width - configManager.paddingRight) reloadContentSize() let contentOffsetX = max(0 , min((contentSize. width - configManager. paddingRight) * offsetScale - halfWidth, contentSize. width - width)) reloadContentOffset(contentOffsetX) scrollViewDidScroll(self) }
Long press gesture panel processing When ScrollView receives a long press or click gesture, we need to show or hide the details panel
1 2 3 4 5 6 7 8 9 10 @objc func longPressSelector (_ gesture: UILongPressGestureRecognizer) { let index = Int(floor(gesture. location(in: self). x / configManager. itemWidth)) selectedIndex = index if (selectedIndex >= configManager. modelArray. count) { selectedIndex = configManager.modelArray.count - 1 } self. setNeedsDisplay() }
Chart magnifying glass We integrate all the charts into the container HTKLineContainerView, where we handle the display of the magnifying glass according to gestures
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func touchesGesture (_ touched: Set<UITouch>, _ state: UIGestureRecognizerState) { guard var location = touched.first?.location(in: self) else { shotView. shotPoint = nil return } var previousLocation = touched.first?.previousLocation(in: self) ?? location location = convertLocation(location) previousLocation = convertLocation(previousLocation) let translation = CGPoint.init(x: location.x - previousLocation.x, y: location.y - previousLocation.y) klineView.drawContext.touchesGesture(location, translation, state) shotView.shotPoint = state != .ended ? touched.first?.location(in: self) : nil }
Finger marker drawing Our finger mark drawings are all drawn through HTDrawContext, we need to pay attention to including parallelograms, we need to convert all coordinates to the current price coordinates, because our zoom gesture needs to keep these finger mark drawings in the correct position
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 func reloadConfigManager (_ configManager: HTKLineConfigManager) { configManager.onDrawItemDidTouch = { [weak self] (drawItem, drawItemIndex) in self?.configManager.shouldReloadDrawItemIndex = drawItemIndex guard let drawItem = drawItem, let colorList = drawItem.drawColor.cgColor.components else { self?.onDrawItemDidTouch?([ "shouldReloadDrawItemIndex" : drawItemIndex, ]) return } self?.onDrawItemDidTouch?([ "shouldReloadDrawItemIndex" : drawItemIndex, "drawColor" : colorList, "drawLineHeight" : drawItem. drawLineHeight, "drawDashWidth" : drawItem. drawDashWidth, "drawDashSpace" : drawItem. drawDashSpace, "drawIsLock" : drawItem. drawIsLock ]) } configManager.onDrawItemComplete = { [weak self] (drawItem, drawItemIndex) in self?.onDrawItemComplete?([AnyHashable: Any].init()) } configManager.onDrawPointComplete = { [weak self] (drawItem, drawItemIndex) in guard let drawItem = drawItem else { return } self?.onDrawPointComplete?([ "pointCount" : drawItem.pointList.count ]) } let reloadIndex = configManager.shouldReloadDrawItemIndex if reloadIndex >= 0 , reloadIndex < klineView.drawContext.drawItemList.count { let drawItem = klineView.drawContext.drawItemList[reloadIndex] drawItem.drawColor = configManager.drawColor drawItem.drawLineHeight = configManager.drawLineHeight drawItem.drawDashWidth = configManager.drawDashWidth drawItem.drawDashSpace = configManager.drawDashSpace drawItem.drawIsLock = configManager.drawIsLock if (configManager. drawShouldTrash) { configManager.shouldReloadDrawItemIndex = HTDrawState.showPencil.rawValue klineView.drawContext.drawItemList.remove(at: reloadIndex) configManager.drawShouldTrash = false } klineView.drawContext.setNeedsDisplay() } if configManager. shouldFixDraw { configManager.shouldFixDraw = false klineView.drawContext.fixDrawItemList() } if (configManager. shouldClearDraw) { configManager.drawType = .none configManager.shouldClearDraw = false klineView.drawContext.clearDrawItemList() } }
Affiliated The full code is here https://github.com/hellohublot/react-native-kline-view I’m hublot, sharing some of my thoughts, I’m too busy with work, it’s inevitable that there will be negligence, please forgive me