From 6ffdf32bf4fece0334c0744163de51f569ef688f Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 15:56:28 +0300 Subject: [PATCH 01/20] apply patch for fix scrolling problem --- .../klinechart/BaseKLineChartView.java | 14 ++++++++++- .../klinechart/ScrollAndScaleView.java | 10 ++++++-- .../container/HTKLineContainerView.java | 6 ++--- ios/Classes/HTKLineView.swift | 23 +++++++++++++++---- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 2f935a6..e007561 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -944,6 +944,18 @@ public void onLongPress(MotionEvent e) { @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { android.util.Log.d("BaseKLineChartView", "onScrollChanged() - l=" + l + ", t=" + t + ", oldl=" + oldl + ", oldt=" + oldt); + + int maxScroll = getMaxScrollX(); + int screenWidth = (int)(getWidth() / getScaleX()); + int realDataMaxScroll = maxScroll - screenWidth; + int maxAllowedScroll = realDataMaxScroll - (int)(MIN_VISIBLE_CANDLES * getmPointWidth()); + int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); + + if (l > normalizedMaxAllowedScroll) { + setScrollX(normalizedMaxAllowedScroll); + return; + } + super.onScrollChanged(l, t, oldl, oldt); } @@ -1110,7 +1122,7 @@ public int getMinScrollX() { } public int getMaxScrollX() { - int contentWidth = (int) Math.max((mDataLen - (mWidth - configManager.paddingRight) / mScaleX), 0); + int contentWidth = (int) Math.max((mDataLen - (mWidth - configManager.paddingRight) / mScaleX + mWidth / mScaleX), 0); return contentWidth; } diff --git a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java index 8ab1668..42cbb5c 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java @@ -16,6 +16,7 @@ public abstract class ScrollAndScaleView extends RelativeLayout implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener { + protected static final int MIN_VISIBLE_CANDLES = 5; protected int mScrollX = 0; protected GestureDetectorCompat mDetector; protected ScaleGestureDetector mScaleDetector; @@ -271,8 +272,13 @@ public boolean isTouch() { * @param scrollX */ public void setScrollX(int scrollX) { - this.mScrollX = scrollX; - scrollTo(scrollX, 0); + int maxScroll = getMaxScrollX(); + int screenWidth = (int)(getWidth() / getScaleX()); + int realDataMaxScroll = maxScroll - screenWidth; + int maxAllowedScroll = realDataMaxScroll - (int)(MIN_VISIBLE_CANDLES * getmPointWidth()); + int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); + this.mScrollX = Math.max(0, Math.min(scrollX, normalizedMaxAllowedScroll)); + scrollTo(this.mScrollX, 0); } /** diff --git a/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java b/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java index c746653..a522597 100644 --- a/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java @@ -71,7 +71,6 @@ public void reloadConfigManager() { klineView.setMTextSize(klineView.configManager.candleTextFontSize); klineView.setMTextColor(klineView.configManager.candleTextColor); klineView.reloadColor(); - Boolean isEnd = klineView.getScrollOffset() >= klineView.getMaxScrollX(); int previousScrollX = klineView.getScrollOffset(); klineView.notifyChanged(); @@ -79,8 +78,9 @@ public void reloadConfigManager() { // 调整滚动位置以补偿新增的数据 int newScrollX = previousScrollX + klineView.configManager.scrollPositionAdjustment; klineView.setScrollX(newScrollX); - } else if (isEnd || klineView.configManager.shouldScrollToEnd) { - klineView.setScrollX(klineView.getMaxScrollX()); + } else if (klineView.configManager.shouldScrollToEnd) { + int scrollToEnd = klineView.getMaxScrollX() - klineView.getWidth(); + klineView.setScrollX(scrollToEnd); } diff --git a/ios/Classes/HTKLineView.swift b/ios/Classes/HTKLineView.swift index 7cac2a3..b24f916 100644 --- a/ios/Classes/HTKLineView.swift +++ b/ios/Classes/HTKLineView.swift @@ -13,6 +13,8 @@ class HTKLineView: UIScrollView { var configManager: HTKLineConfigManager + private let minVisibleCandles: CGFloat = 5 + lazy var drawContext: HTDrawContext = { let drawContext = HTDrawContext.init(self, configManager) return drawContext @@ -98,7 +100,6 @@ class HTKLineView: UIScrollView { childDraw = wrDraw } - let isEnd = contentOffset.x + 1 + bounds.size.width >= contentSize.width let previousContentOffset = contentOffset.x reloadContentSize() @@ -106,8 +107,8 @@ class HTKLineView: UIScrollView { // Adjust scroll position to compensate for newly added data let newContentOffset = previousContentOffset + configManager.scrollPositionAdjustment reloadContentOffset(newContentOffset, false) - } else if configManager.shouldScrollToEnd || isEnd { - let toEndContentOffset = contentSize.width - bounds.size.width + } else if configManager.shouldScrollToEnd { + let toEndContentOffset = contentSize.width - 2 * bounds.size.width let distance = abs(contentOffset.x - toEndContentOffset) let animated = distance <= configManager.itemWidth reloadContentOffset(toEndContentOffset, animated) @@ -150,16 +151,19 @@ class HTKLineView: UIScrollView { let contentWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) + configManager.paddingRight + + bounds.size.width contentSize = CGSize.init(width: contentWidth, height: frame.size.height) } func reloadContentOffset(_ contentOffsetX: CGFloat, _ animated: Bool = false) { - let offsetX = max(0, min(contentOffsetX, contentSize.width - bounds.size.width)) + let allCandlesWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) + let maxAllowedOffset = allCandlesWidth - minVisibleCandles * configManager.itemWidth + let offsetX = max(0, min(contentOffsetX, maxAllowedOffset)) setContentOffset(CGPoint.init(x: offsetX, y: 0), animated: animated) } func smoothScrollToEnd() { - let endOffsetX = contentSize.width - bounds.size.width + let endOffsetX = contentSize.width - 2 * bounds.size.width reloadContentOffset(endOffsetX, true) } @@ -721,7 +725,16 @@ class HTKLineView: UIScrollView { extension HTKLineView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { + let allCandlesWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) + let maxAllowedOffset = allCandlesWidth - minVisibleCandles * configManager.itemWidth + let contentOffsetX = scrollView.contentOffset.x + + if contentOffsetX > maxAllowedOffset { + scrollView.contentOffset.x = maxAllowedOffset + return + } + var visibleStartIndex = Int(floor(contentOffsetX / configManager.itemWidth)) var visibleEndIndex = Int( ceil((contentOffsetX + scrollView.bounds.size.width) / configManager.itemWidth)) From d65ada0109a4b2411e06203faa69fce0c920ee80 Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 16:05:22 +0300 Subject: [PATCH 02/20] added min visible candles to config manager --- .../github/fujianlian/klinechart/BaseKLineChartView.java | 7 ++++++- .../fujianlian/klinechart/HTKLineConfigManager.java | 7 +++++++ .../github/fujianlian/klinechart/ScrollAndScaleView.java | 9 +++++++-- ios/Classes/HTKLineConfigManager.swift | 3 +++ ios/Classes/HTKLineView.swift | 6 ++---- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index e007561..baea4b2 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -948,7 +948,7 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) { int maxScroll = getMaxScrollX(); int screenWidth = (int)(getWidth() / getScaleX()); int realDataMaxScroll = maxScroll - screenWidth; - int maxAllowedScroll = realDataMaxScroll - (int)(MIN_VISIBLE_CANDLES * getmPointWidth()); + int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getmPointWidth()); int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); if (l > normalizedMaxAllowedScroll) { @@ -1126,6 +1126,11 @@ public int getMaxScrollX() { return contentWidth; } + @Override + protected float getMinVisibleCandles() { + return configManager.minVisibleCandles; + } + /** * 在主区域画线 * diff --git a/android/src/main/java/com/github/fujianlian/klinechart/HTKLineConfigManager.java b/android/src/main/java/com/github/fujianlian/klinechart/HTKLineConfigManager.java index 4148528..e4f578e 100644 --- a/android/src/main/java/com/github/fujianlian/klinechart/HTKLineConfigManager.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/HTKLineConfigManager.java @@ -103,6 +103,8 @@ public class HTKLineConfigManager { public float candleCornerRadius = 0; + public float minVisibleCandles = 5; + public int minuteVolumeCandleColor = Color.RED; public float minuteVolumeCandleWidth = 1.5f; @@ -453,6 +455,11 @@ public void reloadOptionList(Map optionList) { this.candleCornerRadius = candleCornerRadiusValue.floatValue(); } + Number minVisibleCandlesValue = (Number)configList.get("minVisibleCandles"); + if (minVisibleCandlesValue != null) { + this.minVisibleCandles = minVisibleCandlesValue.floatValue(); + } + this.fontFamily = (configList.get("fontFamily")).toString(); this.textColor = ((Number) configList.get("textColor")).intValue(); this.headerTextFontSize = ((Number)configList.get("headerTextFontSize")).floatValue(); diff --git a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java index 42cbb5c..5be8a1a 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java @@ -16,8 +16,13 @@ public abstract class ScrollAndScaleView extends RelativeLayout implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener { - protected static final int MIN_VISIBLE_CANDLES = 5; protected int mScrollX = 0; + + /** + * Get minimum visible candles + * @return minimum number of candles that should be visible + */ + protected abstract float getMinVisibleCandles(); protected GestureDetectorCompat mDetector; protected ScaleGestureDetector mScaleDetector; @@ -275,7 +280,7 @@ public void setScrollX(int scrollX) { int maxScroll = getMaxScrollX(); int screenWidth = (int)(getWidth() / getScaleX()); int realDataMaxScroll = maxScroll - screenWidth; - int maxAllowedScroll = realDataMaxScroll - (int)(MIN_VISIBLE_CANDLES * getmPointWidth()); + int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getmPointWidth()); int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); this.mScrollX = Math.max(0, Math.min(scrollX, normalizedMaxAllowedScroll)); scrollTo(this.mScrollX, 0); diff --git a/ios/Classes/HTKLineConfigManager.swift b/ios/Classes/HTKLineConfigManager.swift index 32612eb..26360b9 100644 --- a/ios/Classes/HTKLineConfigManager.swift +++ b/ios/Classes/HTKLineConfigManager.swift @@ -134,6 +134,8 @@ class HTKLineConfigManager: NSObject { var candleCornerRadius: CGFloat = 0 + var minVisibleCandles: CGFloat = 5 + var minuteVolumeCandleWidth: CGFloat = 0 var _minuteVolumeCandleWidth: CGFloat = 0 @@ -448,6 +450,7 @@ class HTKLineConfigManager: NSObject { _minuteVolumeCandleWidth = configList["minuteVolumeCandleWidth"] as? CGFloat ?? 0 _macdCandleWidth = configList["macdCandleWidth"] as? CGFloat ?? 0 candleCornerRadius = configList["candleCornerRadius"] as? CGFloat ?? 0 + minVisibleCandles = configList["minVisibleCandles"] as? CGFloat ?? 5 reloadScrollViewScale(1) paddingTop = configList["paddingTop"] as? CGFloat ?? 0 paddingRight = configList["paddingRight"] as? CGFloat ?? 0 diff --git a/ios/Classes/HTKLineView.swift b/ios/Classes/HTKLineView.swift index b24f916..ea32988 100644 --- a/ios/Classes/HTKLineView.swift +++ b/ios/Classes/HTKLineView.swift @@ -13,8 +13,6 @@ class HTKLineView: UIScrollView { var configManager: HTKLineConfigManager - private let minVisibleCandles: CGFloat = 5 - lazy var drawContext: HTDrawContext = { let drawContext = HTDrawContext.init(self, configManager) return drawContext @@ -157,7 +155,7 @@ class HTKLineView: UIScrollView { func reloadContentOffset(_ contentOffsetX: CGFloat, _ animated: Bool = false) { let allCandlesWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) - let maxAllowedOffset = allCandlesWidth - minVisibleCandles * configManager.itemWidth + let maxAllowedOffset = allCandlesWidth - configManager.minVisibleCandles * configManager.itemWidth let offsetX = max(0, min(contentOffsetX, maxAllowedOffset)) setContentOffset(CGPoint.init(x: offsetX, y: 0), animated: animated) } @@ -726,7 +724,7 @@ extension HTKLineView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let allCandlesWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) - let maxAllowedOffset = allCandlesWidth - minVisibleCandles * configManager.itemWidth + let maxAllowedOffset = allCandlesWidth - configManager.minVisibleCandles * configManager.itemWidth let contentOffsetX = scrollView.contentOffset.x From 832caeaa5f4a9c1878fefb63afb84fac838fe659 Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 16:43:10 +0300 Subject: [PATCH 03/20] add settings to example --- example/App.js | 7 +++++-- example/utils/businessLogic.js | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/example/App.js b/example/App.js index 572f63b..529babd 100644 --- a/example/App.js +++ b/example/App.js @@ -39,6 +39,7 @@ import { const App = () => { + const MIN_VISIBLE_CANDLES = 10 const [isDarkTheme, setIsDarkTheme] = useState(false) const [selectedTimeType, setSelectedTimeType] = useState(2) // Corresponds to 1 minute const [selectedMainIndicator, setSelectedMainIndicator] = useState(1) // Corresponds to MA (1=MA, 2=BOLL) @@ -164,7 +165,8 @@ const App = () => { lastDataLength, currentScrollPosition, showVolumeChart, - candleCornerRadius + candleCornerRadius, + minVisibleCandles: MIN_VISIBLE_CANDLES }, shouldScrollToEnd, kLineViewRef.current ? true : false) setOptionListValue(newOptionList) }, [klineData, selectedMainIndicator, selectedSubIndicator, showVolumeChart, isDarkTheme, selectedTimeType, selectedDrawTool, showIndicatorSelector, showTimeSelector, showDrawToolSelector, drawShouldContinue, optionList, lastDataLength, currentScrollPosition, candleCornerRadius]) @@ -213,7 +215,8 @@ const App = () => { lastDataLength, currentScrollPosition, showVolumeChart, - candleCornerRadius + candleCornerRadius, + minVisibleCandles: MIN_VISIBLE_CANDLES }, false) // Calculate scroll distance adjustment needed (based on item width) diff --git a/example/utils/businessLogic.js b/example/utils/businessLogic.js index 1f66904..acbb500 100644 --- a/example/utils/businessLogic.js +++ b/example/utils/businessLogic.js @@ -282,7 +282,8 @@ export const packOptionList = (modelArray, appState, shouldScrollToEnd = true, u selectedDrawTool, showVolumeChart, candleCornerRadius, - drawShouldContinue + drawShouldContinue, + minVisibleCandles } = appState const theme = ThemeManager.getCurrentTheme(isDarkTheme) @@ -355,6 +356,7 @@ export const packOptionList = (modelArray, appState, shouldScrollToEnd = true, u itemWidth: 8 * pixelRatio, candleWidth: 6 * pixelRatio, candleCornerRadius: candleCornerRadius * pixelRatio, + minVisibleCandles: minVisibleCandles || 5, minuteVolumeCandleColor: processColor(showVolumeChart ? COLOR(0.0941176, 0.509804, 0.831373, 0.501961) : 'transparent'), minuteVolumeCandleWidth: showVolumeChart ? 2 * pixelRatio : 0, macdCandleWidth: 1 * pixelRatio, From 7460078e3f7bf00efe783f260f0b04fa285db96e Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 16:56:13 +0300 Subject: [PATCH 04/20] fix typo --- .../github/fujianlian/klinechart/BaseKLineChartView.java | 2 +- .../github/fujianlian/klinechart/ScrollAndScaleView.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index baea4b2..0550023 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -948,7 +948,7 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) { int maxScroll = getMaxScrollX(); int screenWidth = (int)(getWidth() / getScaleX()); int realDataMaxScroll = maxScroll - screenWidth; - int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getmPointWidth()); + int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getPointWidth()); int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); if (l > normalizedMaxAllowedScroll) { diff --git a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java index 5be8a1a..e0b4314 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java @@ -271,6 +271,13 @@ public boolean isTouch() { */ public abstract int getMaxScrollX(); + /** + * Get the point width + * + * @return + */ + public abstract float getPointWidth(); + /** * Set ScrollX * @@ -280,7 +287,7 @@ public void setScrollX(int scrollX) { int maxScroll = getMaxScrollX(); int screenWidth = (int)(getWidth() / getScaleX()); int realDataMaxScroll = maxScroll - screenWidth; - int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getmPointWidth()); + int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getPointWidth()); int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); this.mScrollX = Math.max(0, Math.min(scrollX, normalizedMaxAllowedScroll)); scrollTo(this.mScrollX, 0); From 271db9da9af2efb0c5a30b9fba4cf5dc6b00e0b3 Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 17:42:09 +0300 Subject: [PATCH 05/20] fix scroll on android --- .../klinechart/BaseKLineChartView.java | 10 +++++---- .../klinechart/ScrollAndScaleView.java | 22 +++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 0550023..8d52d62 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -945,10 +945,7 @@ public void onLongPress(MotionEvent e) { protected void onScrollChanged(int l, int t, int oldl, int oldt) { android.util.Log.d("BaseKLineChartView", "onScrollChanged() - l=" + l + ", t=" + t + ", oldl=" + oldl + ", oldt=" + oldt); - int maxScroll = getMaxScrollX(); - int screenWidth = (int)(getWidth() / getScaleX()); - int realDataMaxScroll = maxScroll - screenWidth; - int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getPointWidth()); + int maxAllowedScroll = (int)(mDataLen - getMinVisibleCandles() * getPointWidth()); int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); if (l > normalizedMaxAllowedScroll) { @@ -1131,6 +1128,11 @@ protected float getMinVisibleCandles() { return configManager.minVisibleCandles; } + @Override + public float getDataLength() { + return mDataLen; + } + /** * 在主区域画线 * diff --git a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java index e0b4314..620ca49 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java @@ -278,16 +278,20 @@ public boolean isTouch() { */ public abstract float getPointWidth(); + /** + * Get the total data length (itemCount * pointWidth) + * + * @return + */ + public abstract float getDataLength(); + /** * Set ScrollX * * @param scrollX */ public void setScrollX(int scrollX) { - int maxScroll = getMaxScrollX(); - int screenWidth = (int)(getWidth() / getScaleX()); - int realDataMaxScroll = maxScroll - screenWidth; - int maxAllowedScroll = realDataMaxScroll - (int)(getMinVisibleCandles() * getPointWidth()); + int maxAllowedScroll = (int)(getDataLength() - getMinVisibleCandles() * getPointWidth()); int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); this.mScrollX = Math.max(0, Math.min(scrollX, normalizedMaxAllowedScroll)); scrollTo(this.mScrollX, 0); @@ -303,13 +307,17 @@ public boolean isMultipleTouch() { } protected void checkAndFixScrollX() { - int contentSizeWidth = (getMaxScrollX()); + float dataLength = getDataLength(); + float minVisibleCandles = getMinVisibleCandles(); + float pointWidth = getPointWidth(); + int maxAllowedScroll = (int)(dataLength - minVisibleCandles * pointWidth); + int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); if (mScrollX < getMinScrollX()) { mScrollX = getMinScrollX(); mScroller.forceFinished(true); - } else if (mScrollX > contentSizeWidth) { - mScrollX = contentSizeWidth; + } else if (mScrollX > normalizedMaxAllowedScroll) { + mScrollX = normalizedMaxAllowedScroll; mScroller.forceFinished(true); if (!mHasTriggeredRightSide) { mHasTriggeredRightSide = true; From 3b11cf0e571b3c16da7359c201903ba0637e0196 Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 18:08:05 +0300 Subject: [PATCH 06/20] fix scroll to end; --- .../klinechart/BaseKLineChartView.java | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 8d52d62..9631446 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -944,14 +944,7 @@ public void onLongPress(MotionEvent e) { @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { android.util.Log.d("BaseKLineChartView", "onScrollChanged() - l=" + l + ", t=" + t + ", oldl=" + oldl + ", oldt=" + oldt); - - int maxAllowedScroll = (int)(mDataLen - getMinVisibleCandles() * getPointWidth()); - int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); - - if (l > normalizedMaxAllowedScroll) { - setScrollX(normalizedMaxAllowedScroll); - return; - } + super.onScrollChanged(l, t, oldl, oldt); } @@ -1689,19 +1682,20 @@ public boolean onSingleTapUp(MotionEvent e) { } public void smoothScrollToEnd() { - int endScrollX = getMaxScrollX(); - int currentScrollX = getScrollOffset(); - int distance = endScrollX - currentScrollX; - - // android.util.Log.d("BaseKLineChartView", "smoothScrollToEnd DEBUG:"); - // android.util.Log.d("BaseKLineChartView", " mDataLen=" + mDataLen + ", mItemCount=" + mItemCount + ", mPointWidth=" + mPointWidth); - // android.util.Log.d("BaseKLineChartView", " mWidth=" + mWidth + ", mScaleX=" + mScaleX + ", paddingRight=" + configManager.paddingRight); - // android.util.Log.d("BaseKLineChartView", " current=" + currentScrollX + ", end=" + endScrollX + ", distance=" + distance); - - // Always scroll to end position, regardless of current position - // This ensures we go to the rightmost position to show the latest data - setScrollX(endScrollX); - // android.util.Log.d("BaseKLineChartView", "Set scroll position to end: " + endScrollX); + int screenWidthInLogicalUnits = (int)(mWidth / mScaleX); + int endScrollX = (int)(mDataLen + configManager.paddingRight - screenWidthInLogicalUnits); + + setScrollXWithoutMinCandlesLimit(Math.max(0, endScrollX)); + } + + /** + * Set scroll position without applying minVisibleCandles limit + */ + private void setScrollXWithoutMinCandlesLimit(int scrollX) { + int oldX = this.mScrollX; + this.mScrollX = Math.max(0, Math.min(scrollX, (int)mDataLen)); + onScrollChanged(this.mScrollX, 0, oldX, 0); + invalidate(); } // Public getter methods for accessing protected fields From f4f2fa9a3601fcb30ee0dec6904ca35e23f08426 Mon Sep 17 00:00:00 2001 From: Artem Slizhik Date: Thu, 15 Jan 2026 18:10:06 +0300 Subject: [PATCH 07/20] fix empty lines --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 9631446..547357d 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -944,8 +944,6 @@ public void onLongPress(MotionEvent e) { @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { android.util.Log.d("BaseKLineChartView", "onScrollChanged() - l=" + l + ", t=" + t + ", oldl=" + oldl + ", oldt=" + oldt); - - super.onScrollChanged(l, t, oldl, oldt); } From b018cc283a3fd38f8d849ce6ee770eaaff86c2a8 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Sun, 15 Mar 2026 21:41:34 +0200 Subject: [PATCH 08/20] Codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cc37007..4852cb2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @furkando @NGabuaeva @EdiOanceaV2 @sinantalhakosar @rturtu @hikmet-demir @Matteoverzotti @robertbarbu27 @snowtema \ No newline at end of file +* @furkando @NGabuaeva @EdiOanceaV2 @sinantalhakosar @rturtu @hikmet-demir @Matteoverzotti @robertbarbu27 \ No newline at end of file From 21e11648746e5c48ebd107bdd18e58f178813d60 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Mon, 16 Mar 2026 11:10:21 +0200 Subject: [PATCH 09/20] Fix devin review --- .../klinechart/BaseKLineChartView.java | 8 ++++++-- .../klinechart/ScrollAndScaleView.java | 17 +++++------------ .../container/HTKLineContainerView.java | 4 ++-- ios/Classes/HTKLineView.swift | 16 +++++++++++++--- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 621b17d..babf853 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1428,8 +1428,12 @@ public int getMinScrollX() { return 0; } + public int getExtraScrollX() { + return (int) (mOverScrollRange / mScaleX); + } + public int getMaxScrollX() { - int contentWidth = (int) Math.max((mDataLen - (mWidth - configManager.paddingRight) / mScaleX + mWidth / mScaleX), 0); + int contentWidth = (int) Math.max((mDataLen - (mWidth - configManager.paddingRight) / mScaleX + getExtraScrollX()), 0); return contentWidth; } @@ -1999,7 +2003,7 @@ public boolean onSingleTapUp(MotionEvent e) { } public void smoothScrollToEnd() { - int screenWidthInLogicalUnits = (int)(mWidth / mScaleX); + int screenWidthInLogicalUnits = getExtraScrollX(); int endScrollX = (int)(mDataLen + configManager.paddingRight - screenWidthInLogicalUnits); setScrollXWithoutMinCandlesLimit(Math.max(0, endScrollX)); diff --git a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java index 620ca49..8ca9f34 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/ScrollAndScaleView.java @@ -291,10 +291,8 @@ public boolean isTouch() { * @param scrollX */ public void setScrollX(int scrollX) { - int maxAllowedScroll = (int)(getDataLength() - getMinVisibleCandles() * getPointWidth()); - int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); - this.mScrollX = Math.max(0, Math.min(scrollX, normalizedMaxAllowedScroll)); - scrollTo(this.mScrollX, 0); + this.mScrollX = scrollX; + scrollTo(scrollX, 0); } /** @@ -307,17 +305,12 @@ public boolean isMultipleTouch() { } protected void checkAndFixScrollX() { - float dataLength = getDataLength(); - float minVisibleCandles = getMinVisibleCandles(); - float pointWidth = getPointWidth(); - int maxAllowedScroll = (int)(dataLength - minVisibleCandles * pointWidth); - int normalizedMaxAllowedScroll = Math.max(0, maxAllowedScroll); - + int contentSizeWidth = (getMaxScrollX()); if (mScrollX < getMinScrollX()) { mScrollX = getMinScrollX(); mScroller.forceFinished(true); - } else if (mScrollX > normalizedMaxAllowedScroll) { - mScrollX = normalizedMaxAllowedScroll; + } else if (mScrollX > contentSizeWidth) { + mScrollX = contentSizeWidth; mScroller.forceFinished(true); if (!mHasTriggeredRightSide) { mHasTriggeredRightSide = true; diff --git a/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java b/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java index c7ff6e4..021ef49 100644 --- a/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java @@ -87,7 +87,7 @@ public void reloadConfigManager() { int newScrollX = previousScrollX + klineView.configManager.scrollPositionAdjustment; klineView.setScrollX(newScrollX); } else if (klineView.configManager.shouldScrollToEnd) { - int scrollToEnd = klineView.getMaxScrollX() - klineView.getWidth(); + int scrollToEnd = klineView.getMaxScrollX() - klineView.getExtraScrollX(); klineView.setScrollX(scrollToEnd); } @@ -410,7 +410,7 @@ public void addCandlesticksAtTheEnd(ReadableArray candlesticksArray) { try { // Check if user is currently at the end of the chart - boolean wasAtEnd = klineView.getScrollOffset() >= klineView.getMaxScrollX() - 10; + boolean wasAtEnd = klineView.getScrollOffset() >= klineView.getMaxScrollX() - 10 - klineView.getExtraScrollX(); // Get existing model for preserving indicator lists structure KLineEntity templateEntity = null; diff --git a/ios/Classes/HTKLineView.swift b/ios/Classes/HTKLineView.swift index 6a1c7b3..514e1e3 100644 --- a/ios/Classes/HTKLineView.swift +++ b/ios/Classes/HTKLineView.swift @@ -134,11 +134,21 @@ class HTKLineView: UIScrollView { let previousContentOffset = contentOffset.x reloadContentSize() + + let rightScreenOffset = contentOffset.x + bounds.size.width + 1 + let lastCandlestickOffset = contentSize.width - bounds.size.width - configManager.itemWidth / 2 + // how many candlesticks +- should it consider to auto scroll to end when new data is added + // if the user is over-scrolled, then the candlesticks have space to appear on screen without scrolling + // and at some point it will enter this range and become auto-scrolling to end + let candlesticksCountOffset = 1.5 * configManager.itemWidth + // Extra spacing at the end is bounds.size.width + let isEnd = lastCandlestickOffset - candlesticksCountOffset <= rightScreenOffset && rightScreenOffset <= lastCandlestickOffset + candlesticksCountOffset + if configManager.shouldAdjustScrollPosition { // Adjust scroll position to compensate for newly added data let newContentOffset = previousContentOffset + configManager.scrollPositionAdjustment reloadContentOffset(newContentOffset, false) - } else if configManager.shouldScrollToEnd { + } else if configManager.shouldScrollToEnd || isEnd { let toEndContentOffset = contentSize.width - 2 * bounds.size.width let distance = abs(contentOffset.x - toEndContentOffset) let animated = distance <= configManager.itemWidth @@ -188,7 +198,7 @@ class HTKLineView: UIScrollView { func reloadContentOffset(_ contentOffsetX: CGFloat, _ animated: Bool = false) { let allCandlesWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) - let maxAllowedOffset = allCandlesWidth - configManager.minVisibleCandles * configManager.itemWidth + let maxAllowedOffset = max(0, allCandlesWidth - configManager.minVisibleCandles * configManager.itemWidth) let offsetX = max(0, min(contentOffsetX, maxAllowedOffset)) setContentOffset(CGPoint.init(x: offsetX, y: 0), animated: animated) } @@ -1037,7 +1047,7 @@ extension HTKLineView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let allCandlesWidth = configManager.itemWidth * CGFloat(configManager.modelArray.count) - let maxAllowedOffset = allCandlesWidth - configManager.minVisibleCandles * configManager.itemWidth + let maxAllowedOffset = max(0, allCandlesWidth - configManager.minVisibleCandles * configManager.itemWidth) let contentOffsetX = scrollView.contentOffset.x From 02402dd4fcd8821ec80b2fa9730de1503091d927 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Mon, 16 Mar 2026 13:58:37 +0200 Subject: [PATCH 10/20] Fix android getExtraScrollX --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index babf853..c221fe3 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1429,7 +1429,7 @@ public int getMinScrollX() { } public int getExtraScrollX() { - return (int) (mOverScrollRange / mScaleX); + return (int) (mWidth / mScaleX); } public int getMaxScrollX() { From a44b1d64aac0686b139b28edd4b0676f4a1130ff Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Mon, 16 Mar 2026 18:43:19 +0200 Subject: [PATCH 11/20] Fix android scroll too much --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index c221fe3..b60bc1f 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1429,7 +1429,7 @@ public int getMinScrollX() { } public int getExtraScrollX() { - return (int) (mWidth / mScaleX); + return (int) ((mWidth - getMinVisibleCandles() * getPointWidth()) / mScaleX); } public int getMaxScrollX() { From 6fce92de1fb13ad27adc0e82e2efaf9e6ef00d03 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Tue, 17 Mar 2026 16:17:37 +0200 Subject: [PATCH 12/20] Fix getExtraScrollX --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index b60bc1f..4d276c8 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1429,7 +1429,7 @@ public int getMinScrollX() { } public int getExtraScrollX() { - return (int) ((mWidth - getMinVisibleCandles() * getPointWidth()) / mScaleX); + return (int) (mWidth / mScaleX - getMinVisibleCandles() * getPointWidth()); } public int getMaxScrollX() { From 7ad40b2421944845c3783dd352d1173e31fd308e Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Tue, 17 Mar 2026 17:35:55 +0200 Subject: [PATCH 13/20] Fix --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 4d276c8..35ed3ca 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1429,7 +1429,7 @@ public int getMinScrollX() { } public int getExtraScrollX() { - return (int) (mWidth / mScaleX - getMinVisibleCandles() * getPointWidth()); + return (int) (mWidth / mScaleX - getMinVisibleCandles()); } public int getMaxScrollX() { From 42804131b28689b82af5247395619305cda33181 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Wed, 18 Mar 2026 11:34:09 +0200 Subject: [PATCH 14/20] Fix --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index 35ed3ca..e563368 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1429,7 +1429,7 @@ public int getMinScrollX() { } public int getExtraScrollX() { - return (int) (mWidth / mScaleX - getMinVisibleCandles()); + return (int) (mWidth / mScaleX - getMinVisibleCandles() * mScaleX); } public int getMaxScrollX() { From 9888e3a5b3b8673869f291fbaa9985c4cb434904 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Wed, 18 Mar 2026 18:05:49 +0200 Subject: [PATCH 15/20] Fix android scroll --- .../com/github/fujianlian/klinechart/BaseKLineChartView.java | 5 ++++- .../klinechart/container/HTKLineContainerView.java | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java index e563368..1cee40c 100755 --- a/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/BaseKLineChartView.java @@ -1429,7 +1429,10 @@ public int getMinScrollX() { } public int getExtraScrollX() { - return (int) (mWidth / mScaleX - getMinVisibleCandles() * mScaleX); + float minVisibleCandles = getMinVisibleCandles(); + int extraScrollX = (int) (mWidth / mScaleX - Math.min(minVisibleCandles * mPointWidth / mScaleX, mWidth / mScaleX * 0.8f)); + // android.util.Log.d("BaseKLineChartView", "getExtraScrollX: " + extraScrollX + ", mScaleX: " + mScaleX + ", minVisibleCandles: " + minVisibleCandles + ", mWidth: " + mWidth); + return extraScrollX; } public int getMaxScrollX() { diff --git a/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java b/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java index 021ef49..f7028ae 100644 --- a/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java +++ b/android/src/main/java/com/github/fujianlian/klinechart/container/HTKLineContainerView.java @@ -409,8 +409,9 @@ public void addCandlesticksAtTheEnd(ReadableArray candlesticksArray) { } try { + float endPosition = klineView.getMaxScrollX() - klineView.getExtraScrollX(); // Check if user is currently at the end of the chart - boolean wasAtEnd = klineView.getScrollOffset() >= klineView.getMaxScrollX() - 10 - klineView.getExtraScrollX(); + boolean wasAtEnd = endPosition - 10 <= klineView.getScrollOffset() && klineView.getScrollOffset() <= endPosition + 10; // Get existing model for preserving indicator lists structure KLineEntity templateEntity = null; @@ -469,7 +470,7 @@ public void run() { @Override public void run() { android.util.Log.d("HTKLineContainerView", "Scrolling to end after adding new data"); - klineView.setScrollX(klineView.getMaxScrollX()); + klineView.setScrollX(klineView.getMaxScrollX() - klineView.getExtraScrollX()); } }, 100); // Additional delay for scroll } From 33244026accad1536fcd16d6fffc87a1f51bc55e Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Thu, 19 Mar 2026 10:25:19 +0200 Subject: [PATCH 16/20] Bigger limits --- README.md | 21 +++++++++++++++++++++ ios/Classes/HTKLineView.swift | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4fe5ee..a6509ef 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,25 @@ # React Native KLine View + +## Development + +To start the project for: + + - Android: + - in `example` dir + - `yarn install --force` + - `yarn android` + + - ios: + - in `example` dir: `yarn install --force` + - in `example/ios` dir: `pod install` + - in `example` dir: `yarn ios` + - sometimes it throws code developer signature, dont know when or how to fix, complete here if you find out more + + + +## Intro +
React Native KLine View
@@ -74,6 +94,7 @@ yarn add react-native-kline-view@https://github.com/hellohublot/react-native-kli ``` ### iOS Setup + ```bash cd ios && pod install ``` diff --git a/ios/Classes/HTKLineView.swift b/ios/Classes/HTKLineView.swift index 514e1e3..adc4c08 100644 --- a/ios/Classes/HTKLineView.swift +++ b/ios/Classes/HTKLineView.swift @@ -136,11 +136,11 @@ class HTKLineView: UIScrollView { let rightScreenOffset = contentOffset.x + bounds.size.width + 1 - let lastCandlestickOffset = contentSize.width - bounds.size.width - configManager.itemWidth / 2 + let lastCandlestickOffset = contentSize.width - configManager.paddingRight - configManager.itemWidth / 2 // how many candlesticks +- should it consider to auto scroll to end when new data is added // if the user is over-scrolled, then the candlesticks have space to appear on screen without scrolling // and at some point it will enter this range and become auto-scrolling to end - let candlesticksCountOffset = 1.5 * configManager.itemWidth + let candlesticksCountOffset = 3 * configManager.itemWidth // Extra spacing at the end is bounds.size.width let isEnd = lastCandlestickOffset - candlesticksCountOffset <= rightScreenOffset && rightScreenOffset <= lastCandlestickOffset + candlesticksCountOffset From 0b612b22d2fa0b0e4b0c0b50ac7beacb4cbc8b42 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Thu, 19 Mar 2026 12:03:51 +0200 Subject: [PATCH 17/20] Fix ios scroll at end --- ios/Classes/HTKLineContainerView.swift | 12 +++++++++--- ios/Classes/HTKLineView.swift | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ios/Classes/HTKLineContainerView.swift b/ios/Classes/HTKLineContainerView.swift index 885ed43..31fbebf 100644 --- a/ios/Classes/HTKLineContainerView.swift +++ b/ios/Classes/HTKLineContainerView.swift @@ -351,8 +351,14 @@ class HTKLineContainerView: UIView { print("HTKLineContainerView: Using indicator data from React Native - maList.count=\(newModel.maList.count), maVolumeList.count=\(newModel.maVolumeList.count)") } - // Get the scroll position before adding data - let wasAtEnd = klineView.contentOffset.x >= (klineView.contentSize.width - klineView.frame.width - 10) + let rightScreenOffset = klineView.contentOffset.x + bounds.size.width + 1 - configManager.paddingRight + let lastCandlestickOffset = configManager.itemWidth * CGFloat(configManager.modelArray.count) - configManager.itemWidth / 2 + // how many candlesticks +- should it consider to auto scroll to end when new data is added + // if the user is over-scrolled, then the candlesticks have space to appear on screen without scrolling + // and at some point it will enter this range and become auto-scrolling to end + let candlesticksCountOffset = 3 * configManager.itemWidth + // Extra spacing at the end is bounds.size.width + let wasAtEnd = lastCandlestickOffset - candlesticksCountOffset <= rightScreenOffset && rightScreenOffset <= lastCandlestickOffset + candlesticksCountOffset // Add new models to the end of the array configManager.modelArray.append(contentsOf: newModels) @@ -375,7 +381,7 @@ class HTKLineContainerView: UIView { if wasAtEnd { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { print("HTKLineContainerView: Scrolling to end after adding new data") - let maxContentOffsetX = max(0, self.klineView.contentSize.width - self.klineView.bounds.size.width) + let maxContentOffsetX = max(0, self.klineView.contentSize.width - 2 * self.klineView.bounds.size.width) self.klineView.reloadContentOffset(maxContentOffsetX, true) } } diff --git a/ios/Classes/HTKLineView.swift b/ios/Classes/HTKLineView.swift index adc4c08..ab66ac1 100644 --- a/ios/Classes/HTKLineView.swift +++ b/ios/Classes/HTKLineView.swift @@ -135,8 +135,8 @@ class HTKLineView: UIScrollView { reloadContentSize() - let rightScreenOffset = contentOffset.x + bounds.size.width + 1 - let lastCandlestickOffset = contentSize.width - configManager.paddingRight - configManager.itemWidth / 2 + let rightScreenOffset = contentOffset.x + bounds.size.width + 1 - configManager.paddingRight + let lastCandlestickOffset = configManager.itemWidth * CGFloat(configManager.modelArray.count) - configManager.itemWidth / 2 // how many candlesticks +- should it consider to auto scroll to end when new data is added // if the user is over-scrolled, then the candlesticks have space to appear on screen without scrolling // and at some point it will enter this range and become auto-scrolling to end @@ -144,6 +144,12 @@ class HTKLineView: UIScrollView { // Extra spacing at the end is bounds.size.width let isEnd = lastCandlestickOffset - candlesticksCountOffset <= rightScreenOffset && rightScreenOffset <= lastCandlestickOffset + candlesticksCountOffset + // Additional context for debugging over-scroll + let contentWidth = contentSize.width + let maxScrollOffset = max(0, contentWidth - bounds.size.width) + let isOverScrolled = contentOffset.x >= maxScrollOffset + let distanceFromEnd = maxScrollOffset - contentOffset.x + if configManager.shouldAdjustScrollPosition { // Adjust scroll position to compensate for newly added data let newContentOffset = previousContentOffset + configManager.scrollPositionAdjustment From f3d32053a20f890e72cb6e1561f774d058fd557f Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Thu, 19 Mar 2026 12:33:36 +0200 Subject: [PATCH 18/20] Fix ios overscroll --- ios/Classes/HTKLineContainerView.swift | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ios/Classes/HTKLineContainerView.swift b/ios/Classes/HTKLineContainerView.swift index 31fbebf..3b24075 100644 --- a/ios/Classes/HTKLineContainerView.swift +++ b/ios/Classes/HTKLineContainerView.swift @@ -358,32 +358,41 @@ class HTKLineContainerView: UIView { // and at some point it will enter this range and become auto-scrolling to end let candlesticksCountOffset = 3 * configManager.itemWidth // Extra spacing at the end is bounds.size.width - let wasAtEnd = lastCandlestickOffset - candlesticksCountOffset <= rightScreenOffset && rightScreenOffset <= lastCandlestickOffset + candlesticksCountOffset + let wasAtEnd = lastCandlestickOffset - candlesticksCountOffset <= rightScreenOffset && rightScreenOffset <= lastCandlestickOffset + candlesticksCountOffset + configManager.paddingRight + + let isOverscrolled = rightScreenOffset > lastCandlestickOffset + candlesticksCountOffset // Add new models to the end of the array configManager.modelArray.append(contentsOf: newModels) - print("HTKLineContainerView: Added \(newModels.count) new candlesticks to the end") - print("HTKLineContainerView: Total candlesticks now: \(configManager.modelArray.count)") - print("HTKLineContainerView: Was at end before adding: \(wasAtEnd)") + // print("HTKLineContainerView: Added \(newModels.count) new candlesticks to the end") + // print("HTKLineContainerView: Total candlesticks now: \(configManager.modelArray.count)") + // print("HTKLineContainerView: Was at end before adding: \(wasAtEnd)") // Force redraw and optionally scroll to end DispatchQueue.main.async { [weak self] in guard let self = self else { return } - print("HTKLineContainerView: Reloading content size after adding candlesticks") + // print("HTKLineContainerView: Reloading content size after adding candlesticks") self.klineView.reloadContentSize() - print("HTKLineContainerView: Triggering redraw after adding candlesticks") + // print("HTKLineContainerView: Triggering redraw after adding candlesticks") self.klineView.setNeedsDisplay() // If user was at the end, keep them at the end if wasAtEnd { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - print("HTKLineContainerView: Scrolling to end after adding new data") + // print("HTKLineContainerView: Scrolling to end after adding new data") let maxContentOffsetX = max(0, self.klineView.contentSize.width - 2 * self.klineView.bounds.size.width) self.klineView.reloadContentOffset(maxContentOffsetX, true) } + } else if isOverscrolled { + // If user is overscrolled, force redraw to ensure new candlesticks appear + // print("HTKLineContainerView: User is overscrolled, forcing redraw for new candlesticks") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + // print("HTKLineContainerView: Scrolling to end after adding new data") + self.klineView.reloadContentOffset(self.klineView.contentOffset.x + self.configManager.itemWidth / 2, true) + } } } } catch { From d5e235e8ccfa2978995ecbb9b974c7fc583df8b8 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Thu, 19 Mar 2026 12:37:19 +0200 Subject: [PATCH 19/20] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6509ef..3f6cd65 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ To start the project for: - in `example` dir: `yarn install --force` - in `example/ios` dir: `pod install` - in `example` dir: `yarn ios` - - sometimes it throws code developer signature, dont know when or how to fix, complete here if you find out more + - sometimes it throws code developer signature. if it shows the application started successfully means the simulator works fine From 11378962bc3d83d43f26245a76abb91734e40298 Mon Sep 17 00:00:00 2001 From: Razvan Turturica Date: Thu, 19 Mar 2026 12:39:00 +0200 Subject: [PATCH 20/20] Even more readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f6cd65..4196441 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,14 @@ To start the project for: - in `example` dir - `yarn install --force` - `yarn android` + - to see logs: `adb logcat`, needs grep as this prints everything - ios: - in `example` dir: `yarn install --force` - in `example/ios` dir: `pod install` - - in `example` dir: `yarn ios` + - in `example` dir: `yarn ios` (or start from xcode) - sometimes it throws code developer signature. if it shows the application started successfully means the simulator works fine + - to see logs start in xcode