Skip to content

Commit

Permalink
[DF] feat(Shopify#547): experimentalMaintainTopContentPosition work…
Browse files Browse the repository at this point in the history
…s horizontally
  • Loading branch information
friyiajr committed Feb 25, 2023
1 parent 6752c55 commit 68875d0
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 45 deletions.
37 changes: 23 additions & 14 deletions fixture/src/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "react-native";
import { FlashList } from "@shopify/flash-list";
import { useFocusEffect } from "@react-navigation/native";
import { TouchableOpacity } from "react-native-gesture-handler";

interface ListItem {
value: number;
Expand Down Expand Up @@ -73,7 +74,8 @@ const List = () => {
style={{
...styles.container,
backgroundColor,
height: item.value % 2 === 0 ? 100 : 200,
// height: item.value % 2 === 0 ? 100 : 200,
width: item.value % 2 === 0 ? 100 : 200,
}}
>
<Text>Cell Id: {item.value}</Text>
Expand All @@ -84,25 +86,31 @@ const List = () => {

return (
<>
<Button
title="ADD"
<TouchableOpacity
style={{
height: 40,
backgroundColor: "purple",
justifyContent: "center",
alignItems: "center",
}}
onPress={() => {
// setTimeout(() => {
// setIsLoading(false);
setData([{ value: newItemIndexes++ }, ...data]);
// }, 1500);
// setIsLoading(true);
}}
/>
>
<Text style={{ color: "white", fontWeight: "bold", fontSize: 30 }}>
ADD A NEW ITEM
</Text>
</TouchableOpacity>

<FlashList
ref={list}
refreshing={refreshing}
onRefresh={() => {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}}
// onRefresh={() => {
// setRefreshing(true);
// setTimeout(() => {
// setRefreshing(false);
// }, 2000);
// }}
keyExtractor={(item: ListItem) => {
return item.value.toString();
}}
Expand All @@ -113,6 +121,7 @@ const List = () => {
estimatedItemSize={100}
data={data}
drawDistance={250}
horizontal
// ListHeaderComponent={
// <View style={{ height: 100 }}>
// <Text>{isLoading ? "LOADING" : ""}</Text>
Expand Down
88 changes: 57 additions & 31 deletions ios/Sources/AutoLayoutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,42 @@ import UIKit
clearGaps(for: cellContainers)
fixFooter()
}

/// Finds the item with the first stable id and adjusts the scroll view offset based on how much
/// it moved when a new item is added.
private func maintainTopContentPosition(
cellContainers: [CellContainer],
scrollView: UIScrollView?
) {
guard let scrollView = scrollView, !self.isInitialRender else { return }

for cellContainer in cellContainers {
let minValue = horizontal ?
cellContainer.frame.minX :
cellContainer.frame.minY

if cellContainer.layoutType == firstItemStableId {
if minValue != firstItemOffset {
let diff = minValue - firstItemOffset

let currentOffset = horizontal
? scrollView.contentOffset.x
: scrollView.contentOffset.y

let scrollValue = diff + currentOffset

scrollView.contentOffset = CGPoint(
x: horizontal ? scrollValue : 0,
y: horizontal ? 0 : scrollValue
)

// You only need to adjust the scroll view once. Break the
// loop after this
return
}
}
}
}

/// Checks for overlaps or gaps between adjacent items and then applies a correction.
/// Performance: RecyclerListView renders very small number of views and this is not going to trigger multiple layouts on the iOS side.
Expand All @@ -126,11 +162,11 @@ import UIKit
var maxBound: CGFloat = 0
var minBound: CGFloat = CGFloat(Int.max)
var maxBoundNextCell: CGFloat = 0
let correctedScrollOffset = scrollView!.contentOffset.y - (horizontal ? frame.minX : frame.minY)
let correctedScrollOffset = (horizontal ? scrollView!.contentOffset.x : scrollView!.contentOffset.y) - (horizontal ? frame.minX : frame.minY)
lastMaxBoundOverall = 0

var nextFirstItemStableId = ""
var nextFirstItemOffset: CGFloat = 0
var nextFirstItemOffset: CGFloat = 0

cellContainers.indices.dropLast().forEach { index in
let cellContainer = cellContainers[index]
Expand Down Expand Up @@ -199,43 +235,33 @@ import UIKit
maxBoundNextCell = max(maxBound, nextCell.frame.maxY)
}
}
if(nextFirstItemStableId == "" || nextCell.layoutType == firstItemStableId) {
nextFirstItemOffset = nextCell.frame.minY

// This state update is used for maintainTopContentPosition only.
// This is ignored during normal use cases
if (
nextFirstItemStableId == "" ||
nextCell.layoutType == firstItemStableId
) {
nextFirstItemOffset = horizontal ?
nextCell.frame.minX :
nextCell.frame.minY

nextFirstItemStableId = nextCell.layoutType
}

updateLastMaxBoundOverall(currentCell: cellContainer, nextCell: nextCell)
}

// This was placed here so that offset adjustments would ONLY be performed after
// all necessary views were pulled up to remove the white space
cellContainers.indices.forEach { index in
let cellContainer = cellContainers[index]

if cellContainer.layoutType == firstItemStableId {
if cellContainers[index].frame.minY != firstItemOffset {
print("TAG: firstItemStableId: \(firstItemStableId)")
print("TAG: nextFirstItemStableId: \(nextFirstItemStableId)")

let diff = cellContainers[index].frame.minY - firstItemOffset

print("TAG: diff: \(diff)")

if let scrollView = scrollView, !self.isInitialRender {
let newValue = scrollView.contentOffset.y + diff
print("NewValue \(newValue)")
let newScrollHeight = superview!.frame.height - scrollView.frame.height
print("New Scroll Height \(newScrollHeight)")
scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y + diff)
print("Content Offset \(scrollView.contentOffset.y)")
}
}
}
}
// IF experimental_maintainTopContentPosition = true
maintainTopContentPosition(
cellContainers: cellContainers,
scrollView: scrollView
)

firstItemStableId = nextFirstItemStableId
firstItemOffset = nextFirstItemOffset
lastMaxBound = maxBoundNextCell
lastMinBound = minBound
firstItemStableId = nextFirstItemStableId
firstItemOffset = nextFirstItemOffset
}

private func updateLastMaxBoundOverall(currentCell: CellContainer, nextCell: CellContainer) {
Expand Down

0 comments on commit 68875d0

Please sign in to comment.