Skip to content
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
17 changes: 17 additions & 0 deletions Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
5131A5B82F120ED2004214DF /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 5131A5B72F120ED2004214DF /* Alamofire */; };
51DB7DE72F211C9D003095AC /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 51DB7DE62F211C9D003095AC /* Lottie */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -41,6 +42,7 @@
buildActionMask = 2147483647;
files = (
5131A5B82F120ED2004214DF /* Alamofire in Frameworks */,
51DB7DE72F211C9D003095AC /* Lottie in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -84,6 +86,7 @@
name = "Cherrish-iOS";
packageProductDependencies = (
5131A5B72F120ED2004214DF /* Alamofire */,
51DB7DE62F211C9D003095AC /* Lottie */,
);
productName = "Cherrish-iOS";
productReference = 51B5D3182F0514390090B8B4 /* Cherrish-iOS.app */;
Expand Down Expand Up @@ -115,6 +118,7 @@
minimizedProjectReferenceProxies = 1;
packageReferences = (
5131A5B62F120ED2004214DF /* XCRemoteSwiftPackageReference "Alamofire" */,
51DB7DE52F211C9D003095AC /* XCRemoteSwiftPackageReference "lottie-ios" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 51B5D3192F0514390090B8B4 /* Products */;
Expand Down Expand Up @@ -378,6 +382,14 @@
minimumVersion = 5.11.0;
};
};
51DB7DE52F211C9D003095AC /* XCRemoteSwiftPackageReference "lottie-ios" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/airbnb/lottie-ios";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.6.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
Expand All @@ -386,6 +398,11 @@
package = 5131A5B62F120ED2004214DF /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
51DB7DE62F211C9D003095AC /* Lottie */ = {
isa = XCSwiftPackageProductDependency;
package = 51DB7DE52F211C9D003095AC /* XCRemoteSwiftPackageReference "lottie-ios" */;
productName = Lottie;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 51B5D3102F0514390090B8B4 /* Project object */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct CalendarCellView: View {
}

if calendarMode == .selectedProcedure && isDDay {
Image(.dday)
Image(.calendarDDay)
.resizable()
.frame(width: 38.adjustedW, height: 16.adjustedH)
.padding(.top, 36.adjustedH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import SwiftUI

import Lottie

struct ChallengeLoadingView: View {
@ObservedObject var viewModel: CreateChallengeViewModel

Expand All @@ -16,11 +18,19 @@ struct ChallengeLoadingView: View {
highlight(highlightText: viewModel.selectedRoutine?.description ?? "", normalText: "방향을 바탕으로")
.padding(.top, 113.adjustedH)
TypographyText("TO-DO 미션을 만들고 있어요.", style: .title1_sb_18, color: .gray800)
Image(.loading)
.padding(.top, 17.adjustedH)

LottieView(animationName: "splash", loopMode: .loop)
.frame(width: 130.adjustedW, height: 154.adjustedH)
.padding(.top, 60.adjustedH)

Spacer()
.frame(height: 80.adjustedH)

TypographyText("잠시만 기다려주세요!", style: .title2_sb_16, color: .gray800)
.padding(.top, 17.adjustedH)

Spacer()

TypographyText("AI가 맞춤형 루틴을 제작하고 있어요.", style: .body3_m_12, color: .gray600)
.padding(.bottom, 30.adjustedH)
Comment on lines +21 to 35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Reduce Motion 사용자 배려 추가 권장.
반복 루프 애니메이션은 Reduce Motion이 켜진 경우 1회 재생(또는 정적 대체)로 전환하는 편이 접근성에 유리합니다. 애니메이션이 보이스오버 포커스를 받지 않도록 숨김 처리도 권장합니다.

♿ 제안 변경사항
 struct ChallengeLoadingView: View {
+    `@Environment`(\.accessibilityReduceMotion) private var reduceMotion
     `@ObservedObject` var viewModel: CreateChallengeViewModel   
 
     var body: some View {
         VStack {
@@
-                LottieView(animationName: "splash", loopMode: .loop)
+                LottieView(animationName: "splash", loopMode: reduceMotion ? .playOnce : .loop)
                     .frame(width: 130.adjustedW, height: 154.adjustedH)
                     .padding(.top, 60.adjustedH)
+                    .accessibilityHidden(true)
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeLoadingView.swift`
around lines 21 - 35, The Lottie animation should respect Reduce Motion and
avoid VoiceOver focus: update ChallengeLoadingView where
LottieView(animationName: "splash", loopMode: .loop) is used to use
UIAccessibility.isReduceMotionEnabled (or
`@Environment`(\.accessibilityReduceMotion)) to switch loopMode to a single play
(or replace with a static image) when Reduce Motion is enabled, and add
accessibilityHidden(true) (or .accessibility(hidden: true)) to the LottieView so
it is not focused by VoiceOver; keep the existing layout and text elements
unchanged and only change the LottieView instantiation/attributes.

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

import SwiftUI

import Lottie

struct SplashView: View {
@EnvironmentObject private var appCoordinator: AppCoordinator

private let userDefaultService: UserDefaultService

init(userDefaultService: UserDefaultService = DefaultUserDefaultService()) {
self.userDefaultService = userDefaultService
}
Expand All @@ -27,7 +29,8 @@ struct SplashView: View {
endPoint: .bottomTrailing
)

Image(.appicon)
LottieView(animationName: "splash", loopMode: .playOnce)
.frame(width: 130.adjustedW, height: 154.adjustedH)
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Lottie+.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import SwiftUI
import Lottie

struct LottieView: UIViewRepresentable {
let animationName: String
let loopMode: LottieLoopMode

func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)

let animationView = LottieAnimationView(name: animationName)
animationView.loopMode = loopMode
animationView.play()

animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)

NSLayoutConstraint.activate([
animationView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
animationView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
animationView.topAnchor.constraint(equalTo: view.topAnchor),
animationView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])

return view
}

func updateUIView(_ uiView: UIView, context: Context) {}
Comment on lines +15 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "Lottie+.swift" -type f

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 141


🏁 Script executed:

fd "Lottie\+\.swift" -type f

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 240


🏁 Script executed:

cat -n "./Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Lottie+.swift"

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1331


🌐 Web query:

UIViewRepresentable updateUIView lifecycle SwiftUI best practices

💡 Result:

Key points & best practices for UIViewRepresentable lifecycle (makeUIView / updateUIView)

  1. Lifecycle basics
  • makeUIView(...) is called once to create the UIKit view. updateUIView(_:context:) is called repeatedly whenever SwiftUI considers the representable’s inputs/state changed; after makeUIView it will be called at least once. Use dismantleUIView(...) for cleanup. (developer.apple.com)
  1. Treat updateUIView as a sync/diff method
  • Don’t recreate subviews or do heavy allocation in updateUIView. Apply only the minimal updates needed (set properties, update models, call setNeedsLayout). That keeps SwiftUI diffing fast and avoids flicker. (sarunw.com)
  1. Use Coordinator for delegates, actions and two‑way bindings
  • Put delegate/dataSource handlers and callbacks in Context.coordinator so delegate lifetime and bridging are correct. Use @Binding / @ObservedObject on the SwiftUI side and propagate changes in updateUIView or via the coordinator. (ios-swift-explorations.hashnode.dev)
  1. Be explicit about what triggers updates
  • SwiftUI calls updateUIView when inputs change; if you need a full rebuild, change the view’s .id(...) to force re‑creation. Don’t rely on implicit side effects—drive updates from bindings/observable objects. (stackoverflow.com)
  1. Threading and performance
  • All UIKit work must run on the main thread. Offload heavy processing (parsing, layout calculations) to background threads and apply final UI updates in updateUIView on the main thread. Profile with Instruments if performance is a concern. (peerdh.com)
  1. Cleanup & appearance hooks
  • Use dismantleUIView to remove observers/notifications. Complement representable lifecycle with SwiftUI’s onAppear/onDisappear in the parent view when appropriate. (fatbobman.com)

Short checklist when implementing:

  • makeUIView: create and configure once.
  • updateUIView: apply incremental changes only; read bindings/observables here.
  • Coordinator: implement delegates, send events back to SwiftUI.
  • Use .id to force recreate only when necessary.
  • Keep UIKit updates on main thread; heavy work off the main thread.
  • Clean up in dismantleUIView and/or onDisappear.

If you want, I can show a compact example demonstrating these patterns.

Citations:


updateUIView가 비어 있어 애니메이션 속성 변경이 반영되지 않음

animationName 또는 loopMode가 변경되어도 updateUIView가 구현되지 않았으므로 UI에 반영되지 않습니다. 특히 동일한 LottieView를 다른 파라미터로 재사용할 때 버그가 발생할 수 있습니다.

Coordinator 패턴을 사용하여 animationView 참조를 유지한 후, updateUIView에서 애니메이션과 루프 모드를 갱신해야 합니다.

🛠️ 제안 수정
 struct LottieView: UIViewRepresentable {
     let animationName: String
     let loopMode: LottieLoopMode

+    func makeCoordinator() -> Coordinator { Coordinator() }
+
     func makeUIView(context: Context) -> UIView {
         let view = UIView(frame: .zero)

         let animationView = LottieAnimationView(name: animationName)
+        context.coordinator.animationView = animationView
         animationView.loopMode = loopMode
         animationView.play()

         animationView.translatesAutoresizingMaskIntoConstraints = false
         view.addSubview(animationView)

         NSLayoutConstraint.activate([
             animationView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
             animationView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
             animationView.topAnchor.constraint(equalTo: view.topAnchor),
             animationView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
         ])

         return view
     }

-    func updateUIView(_ uiView: UIView, context: Context) {}
+    func updateUIView(_ uiView: UIView, context: Context) {
+        guard let animationView = context.coordinator.animationView else { return }
+        if animationView.animation?.name != animationName {
+            animationView.animation = LottieAnimation.named(animationName)
+        }
+        animationView.loopMode = loopMode
+        animationView.play()
+    }
+
+    final class Coordinator {
+        var animationView: LottieAnimationView?
+    }
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Lottie`+.swift around
lines 15 - 35, makeUIView currently creates a new LottieAnimationView but
updateUIView is empty, so changes to animationName or loopMode aren’t applied;
implement the Coordinator pattern to store a persistent reference (e.g.,
coordinator.animationView) when creating the view in makeUIView, assign the
created LottieAnimationView to that coordinator property, and then implement
updateUIView to compare and update the coordinator.animationView: if
animationName changed load/set the new LottieAnimationView animation, update
loopMode, and restart/play as needed so parameter changes are reflected without
recreating the whole SwiftUI view.

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,33 +1,153 @@
{
"images" : [
{
"filename" : "app icon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
"filename" : "40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "app icon 1.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
"filename" : "60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "29.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "57.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "57x57"
},
{
"filename" : "114.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "57x57"
},
{
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "40.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "58.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "80.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "50.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "50x50"
},
{
"filename" : "100.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "50x50"
},
{
"filename" : "72.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "72x72"
},
{
"filename" : "144.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "72x72"
},
{
"filename" : "76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "app icon 2.png",
"idiom" : "universal",
"platform" : "ios",
"filename" : "1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "calendar_D-day.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "calendar_D-day@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "calendar_D-day@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading