From 5e10478b8d2cf4b801f81a45256af54cb6feed17 Mon Sep 17 00:00:00 2001 From: swift-man Date: Sat, 27 Jun 2026 14:01:08 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=200.7.1=20reel=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #11 리뷰에서 후속 후보로 남긴 reel 업데이트 경로 검증을 보강합니다. - digit ordinal 매핑을 한 번에 계산하는 digitOrdinalsByIndex를 추가했습니다. - reel target position 계산을 TextColumn 배열 헬퍼로 분리해 테스트 가능한 단위로 만들었습니다. - settingSmoothAnimationRange와 settingReelAnimationRange에서 반복 digitOrdinal 계산을 줄였습니다. - 값이 동일한 digit도 reel revolutions 설정에 따라 다시 회전하는 경로를 테스트로 고정했습니다. - README와 DocC 설치 예시 버전을 0.7.1로 갱신했습니다. 검증: - swift test - DOCS_OUTPUT_PATH=.build/docc-site DOCC_ARCHIVE_PATH=.build/AnimateNumberText.doccarchive ./GeneratingDocumentationSite - git diff --check --- README.md | 4 +- .../AnimateNumberText.md | 2 +- .../AnimateNumberText/Private/TextType.swift | 51 ++++++++++++++++++- .../Public/AnimateNumberText.swift | 20 ++------ .../AnimateNumberTextTests.swift | 30 +++++++++++ 5 files changed, 87 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5418ed6..1eb7a14 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ SwiftUI-Version ![Badge](https://img.shields.io/badge/swift-white.svg?style=flat-square&logo=Swift) ![Badge](https://img.shields.io/badge/SwiftUI-001b87.svg?style=flat-square&logo=Swift&logoColor=black) -![Badge - Version](https://img.shields.io/badge/Version-0.7.0-1177AA?style=flat-square) +![Badge - Version](https://img.shields.io/badge/Version-0.7.1-1177AA?style=flat-square) ![Badge - Swift Package Manager](https://img.shields.io/badge/SPM-compatible-orange?style=flat-square) ![Badge - Platform](https://img.shields.io/badge/platform-mac_12|ios_15|watchos_8|tvos_15-yellow?style=flat-square) ![Badge - License](https://img.shields.io/badge/license-MIT-black?style=flat-square) @@ -171,6 +171,6 @@ Once you have your Swift package set up, adding AnimateNumberText as a dependenc ```swift dependencies: [ - .package(url: "https://github.com/swift-man/AnimateNumberText.git", from: "0.7.0") + .package(url: "https://github.com/swift-man/AnimateNumberText.git", from: "0.7.1") ] ``` diff --git a/Sources/AnimateNumberText/AnimateNumberText.docc/AnimateNumberText.md b/Sources/AnimateNumberText/AnimateNumberText.docc/AnimateNumberText.md index 1fb1fdb..74991f6 100644 --- a/Sources/AnimateNumberText/AnimateNumberText.docc/AnimateNumberText.md +++ b/Sources/AnimateNumberText/AnimateNumberText.docc/AnimateNumberText.md @@ -14,7 +14,7 @@ Add AnimateNumberText to your Swift package dependencies. ```swift dependencies: [ - .package(url: "https://github.com/swift-man/AnimateNumberText.git", from: "0.7.0") + .package(url: "https://github.com/swift-man/AnimateNumberText.git", from: "0.7.1") ] ``` diff --git a/Sources/AnimateNumberText/Private/TextType.swift b/Sources/AnimateNumberText/Private/TextType.swift index f9de879..64268a3 100644 --- a/Sources/AnimateNumberText/Private/TextType.swift +++ b/Sources/AnimateNumberText/Private/TextType.swift @@ -93,6 +93,19 @@ extension Array where Element == TextColumn { filter(\.value.isNumber).count } + func digitOrdinalsByIndex() -> [Int: Int] { + var ordinalsByIndex: [Int: Int] = [:] + ordinalsByIndex.reserveCapacity(digitCount) + var digitOrdinal = 0 + + for index in indices where self[index].value.isNumber { + ordinalsByIndex[index] = digitOrdinal + digitOrdinal += 1 + } + + return ordinalsByIndex + } + func preservingReelPositions(_ positions: [UUID: Double]) -> [UUID: Double] { var nextPositions: [UUID: Double] = [:] @@ -115,16 +128,50 @@ extension Array where Element == TextColumn { return nextPositions } + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + func reelPositions(updatingTo characters: [Character], + animation: AnimateNumberTextAnimation, + currentPositions: [UUID: Double]) -> [UUID: Double] { + let digitOrdinals = digitOrdinalsByIndex() + var nextPositions = preservingReelPositions(currentPositions) + + for (index, character) in characters.enumerated() { + let targetValue = TextType(character) + guard canAnimateDigitChange(to: targetValue, index: index), + let digit = targetValue.digitValue, + let digitOrdinal = digitOrdinals[index] else { + continue + } + + let column = self[index] + let currentPosition = nextPositions[column.id] + ?? Double(column.value.digitValue ?? digit) + nextPositions[column.id] = animation.reelTargetPosition(from: currentPosition, + to: digit, + ordinal: digitOrdinal) + } + + return nextPositions + } + func canAnimateDigitChange(to value: Character, index: Int) -> Bool { + canAnimateDigitChange(to: TextType(value), index: index) + } + + func canAnimateDigitChange(to value: TextType, index: Int) -> Bool { guard indices.contains(index) else { return false } - return self[index].value.isNumber && TextType(value).isNumber + return self[index].value.isNumber && value.isNumber } func needsUpdate(to value: Character, index: Int) -> Bool { + needsUpdate(to: TextType(value), index: index) + } + + func needsUpdate(to value: TextType, index: Int) -> Bool { guard indices.contains(index) else { return false } - return self[index].value != TextType(value) + return self[index].value != value } func digitOrdinal(at index: Int) -> Int? { diff --git a/Sources/AnimateNumberText/Public/AnimateNumberText.swift b/Sources/AnimateNumberText/Public/AnimateNumberText.swift index e0e0222..a7e7246 100644 --- a/Sources/AnimateNumberText/Public/AnimateNumberText.swift +++ b/Sources/AnimateNumberText/Public/AnimateNumberText.swift @@ -254,11 +254,12 @@ public struct AnimateNumberText: View { @MainActor private func settingSmoothAnimationRange(_ characters: [Character]) { let digitCount = animationRange.digitCount + let digitOrdinals = animationRange.digitOrdinalsByIndex() for (index, value) in characters.enumerated() where animationRange.needsUpdate(to: value, index: index) && animationRange.canAnimateDigitChange(to: value, index: index) { - let digitOrdinal = animationRange.digitOrdinal(at: index) ?? index + let digitOrdinal = digitOrdinals[index] ?? index withAnimation(animation.digitAnimation(at: digitOrdinal, digitCount: digitCount)) { @@ -272,23 +273,12 @@ public struct AnimateNumberText: View { @MainActor private func settingReelAnimationRange(_ characters: [Character]) { synchronizeReelPositions() - var nextPositions = reelPositions + let nextPositions = animationRange.reelPositions(updatingTo: characters, + animation: animation, + currentPositions: reelPositions) for (index, value) in characters.enumerated() where animationRange.canAnimateDigitChange(to: value, index: index) { - guard let digit = TextType(value).digitValue, - let digitOrdinal = animationRange.digitOrdinal(at: index) else { - continue - } - - let columnID = animationRange[index].id - let currentPosition = reelPositions[columnID] - ?? Double(animationRange[index].value.digitValue ?? digit) - let targetPosition = animation.reelTargetPosition(from: currentPosition, - to: digit, - ordinal: digitOrdinal) - - nextPositions[columnID] = targetPosition animationRange.set(value, index: index) } diff --git a/Tests/AnimateNumberTextTests/AnimateNumberTextTests.swift b/Tests/AnimateNumberTextTests/AnimateNumberTextTests.swift index 068b619..b681923 100644 --- a/Tests/AnimateNumberTextTests/AnimateNumberTextTests.swift +++ b/Tests/AnimateNumberTextTests/AnimateNumberTextTests.swift @@ -294,6 +294,12 @@ struct AnimateNumberTextTests { #expect(columns.digitOrdinal(at: 2) == 2) #expect(columns.digitOrdinal(at: 3) == nil) #expect(columns.digitOrdinal(at: 4) == 3) + #expect(columns.digitOrdinalsByIndex() == [ + 0: 0, + 1: 1, + 2: 2, + 4: 3 + ]) } @Test @@ -390,4 +396,28 @@ struct AnimateNumberTextTests { #expect(animation.reelTargetPosition(from: 5, to: 5, ordinal: 0) == 15) #expect(animation.reelTargetPosition(from: 5, to: 5, ordinal: 1) == -5) } + + @Test + func reelPositionsSpinUnchangedDigitsThroughUpdatePath() { + let columns = [ + TextColumn(value: .number(3)), + TextColumn(value: .number(0)), + TextColumn(value: .number(1)), + TextColumn(value: .string(".")), + TextColumn(value: .number(9)) + ] + let animation = AnimateNumberTextAnimation.reel(spinningDuration: 0.9, + settleDuration: 0.25, + revolutions: 1) + let currentPositions = columns.currentDigitPositions() + + let nextPositions = columns.reelPositions(updatingTo: Swift.Array("301.9"), + animation: animation, + currentPositions: currentPositions) + + #expect(nextPositions[columns[0].id] == 13) + #expect(nextPositions[columns[1].id] == -10) + #expect(nextPositions[columns[2].id] == 11) + #expect(nextPositions[columns[4].id] == -1) + } } From 48aafeb5c7cdfcfa37d9169c34f5f610b61c1aa8 Mon Sep 17 00:00:00 2001 From: swift-man Date: Sat, 27 Jun 2026 14:26:18 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=EC=A7=80?= =?UTF-8?q?=EC=A0=81=20=EA=B8=B0=EB=B0=98=20ordinal=20=ED=97=AC=ED=8D=BC?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #12 리뷰에서 지적된 소소한 정리 사항을 반영합니다. - 프로덕션 코드에서 더 이상 사용하지 않는 digitOrdinal(at:) 헬퍼를 제거했습니다. - digitOrdinalsByIndex()에서 reserveCapacity(digitCount)를 제거해 불필요한 추가 순회를 없앴습니다. - 관련 테스트명을 digitOrdinalsByIndex 기준으로 정리했습니다. 검증: - swift test - DOCS_OUTPUT_PATH=.build/docc-site DOCC_ARCHIVE_PATH=.build/AnimateNumberText.doccarchive ./GeneratingDocumentationSite - git diff --check --- Sources/AnimateNumberText/Private/TextType.swift | 7 ------- Tests/AnimateNumberTextTests/AnimateNumberTextTests.swift | 7 +------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/Sources/AnimateNumberText/Private/TextType.swift b/Sources/AnimateNumberText/Private/TextType.swift index 64268a3..0f82f9c 100644 --- a/Sources/AnimateNumberText/Private/TextType.swift +++ b/Sources/AnimateNumberText/Private/TextType.swift @@ -95,7 +95,6 @@ extension Array where Element == TextColumn { func digitOrdinalsByIndex() -> [Int: Int] { var ordinalsByIndex: [Int: Int] = [:] - ordinalsByIndex.reserveCapacity(digitCount) var digitOrdinal = 0 for index in indices where self[index].value.isNumber { @@ -174,12 +173,6 @@ extension Array where Element == TextColumn { return self[index].value != value } - func digitOrdinal(at index: Int) -> Int? { - guard indices.contains(index), self[index].value.isNumber else { return nil } - - return self[..