feat(image): 메인 홈, 채팅 - 톡 페이지에 DownsampledKFImage 적용

- 수평 리스트 HStack → LazyHStack으로 교체해 프리로딩/메모리 개선
This commit is contained in:
Yu Sung
2025-10-23 18:55:53 +09:00
parent 62012bd722
commit 8221746569
12 changed files with 57 additions and 72 deletions

View File

@@ -15,15 +15,10 @@ struct OriginalTabItemView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
KFImage(URL(string: item.imageUrl!)) DownsampledKFImage(
.placeholder { Color.gray.opacity(0.2) } url: URL(string: item.imageUrl!),
.retry(maxCount: 2, interval: .seconds(1)) size: CGSize(width: size, height: size * 432 / 306)
.cancelOnDisappear(true) ).cornerRadius(16)
.resizable()
.scaledToFill()
.frame(width: size, height: size * 432 / 306)
.clipped()
.cornerRadius(16)
Text(item.title) Text(item.title)
.font(.custom(Font.preRegular.rawValue, size: 18)) .font(.custom(Font.preRegular.rawValue, size: 18))

View File

@@ -16,14 +16,10 @@ struct TalkItemView: View {
var body: some View { var body: some View {
HStack(spacing: 13) { HStack(spacing: 13) {
KFImage(URL(string: item.imageUrl)) DownsampledKFImage(
.placeholder { Circle().fill(Color.gray.opacity(0.2)) } url: URL(string: item.imageUrl),
.retry(maxCount: 2, interval: .seconds(1)) size: CGSize(width: 76, height: 76)
.cancelOnDisappear(true) ).clipShape(Circle())
.resizable()
.scaledToFill()
.frame(width: 76, height: 76)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 4) { HStack(spacing: 4) {

View File

@@ -17,12 +17,11 @@ struct ContentItemView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
ZStack(alignment: .top) { ZStack(alignment: .top) {
KFImage(URL(string: item.coverImageUrl)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: item.coverImageUrl),
.resizable() size: CGSize(width: 160, height: 160)
.scaledToFill() )
.frame(width: 160, height: 160, alignment: .top) .cornerRadius(16)
.cornerRadius(16)
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Spacer() Spacer()

View File

@@ -112,13 +112,11 @@ struct ContentMainBannerImageView: View {
var body: some View { var body: some View {
Group { Group {
if let boundURL { if let boundURL {
KFImage(boundURL) DownsampledKFImage(
.cancelOnDisappear(true) url: boundURL,
.downsampling(size: CGSize(width: width, height: height)) size: CGSize(width: width, height: height)
.resizable() )
.scaledToFill() .cornerRadius(4.7)
.frame(width: width, height: height)
.cornerRadius(4.7)
} else { } else {
Color.clear Color.clear
.frame(width: width, height: height) .frame(width: width, height: height)

View File

@@ -56,7 +56,7 @@ struct DayOfWeekSeriesView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 5) { LazyHStack(spacing: 5) {
ForEach(0..<dayOfWeekItems.count, id: \.self) { ForEach(0..<dayOfWeekItems.count, id: \.self) {
let item = dayOfWeekItems[$0] let item = dayOfWeekItems[$0]
DayOfWeekDayView(dayOfWeek: item.dayOfWeekStr, isSelected: dayOfWeek == item.dayOfWeek) DayOfWeekDayView(dayOfWeek: item.dayOfWeekStr, isSelected: dayOfWeek == item.dayOfWeek)
@@ -72,7 +72,7 @@ struct DayOfWeekSeriesView: View {
} }
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<seriesList.count, id: \.self) { ForEach(0..<seriesList.count, id: \.self) {
SeriesItemView(item: seriesList[$0]) SeriesItemView(item: seriesList[$0])
} }

View File

@@ -17,13 +17,12 @@ struct SeriesItemView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
ZStack { ZStack {
KFImage(URL(string: item.coverImage)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: item.coverImage),
.resizable() size: CGSize(width: 160, height: 227)
.scaledToFill() )
.frame(width: 160, height: 227, alignment: .center) .cornerRadius(16)
.cornerRadius(16) .clipped()
.clipped()
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) { HStack(spacing: 0) {

View File

@@ -22,12 +22,11 @@ struct HomeCreatorRankingItemView: View {
var body: some View { var body: some View {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
VStack(spacing: 0) { VStack(spacing: 0) {
KFImage(URL(string: item.profileImageUrl)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: item.profileImageUrl),
.resizable() size: CGSize(width: 84, height: 84)
.scaledToFill() )
.frame(width: 84, height: 84, alignment: .top) .clipShape(Circle())
.clipShape(Circle())
Text(item.nickname) Text(item.nickname)
.font(.custom(Font.preRegular.rawValue, size: 16)) .font(.custom(Font.preRegular.rawValue, size: 16))

View File

@@ -17,11 +17,11 @@ struct HomeLiveItemView: View {
HStack(spacing: 16) { HStack(spacing: 16) {
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
ZStack { ZStack {
KFImage(URL(string: item.creatorProfileImage)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: item.creatorProfileImage),
.resizable() size: CGSize(width: 62, height: 62)
.frame(width: 62, height: 62) )
.clipShape(Circle()) .clipShape(Circle())
} }
.padding(7) .padding(7)
.overlay { .overlay {

View File

@@ -93,7 +93,7 @@ struct HomeTabView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<viewModel.liveList.count, id: \.self) { index in ForEach(0..<viewModel.liveList.count, id: \.self) { index in
HomeLiveItemView(item: viewModel.liveList[index]) { roomId in HomeLiveItemView(item: viewModel.liveList[index]) { roomId in
if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
@@ -134,7 +134,7 @@ struct HomeTabView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<viewModel.creatorRanking.count, id: \.self) { ForEach(0..<viewModel.creatorRanking.count, id: \.self) {
let item = viewModel.creatorRanking[$0] let item = viewModel.creatorRanking[$0]
HomeCreatorRankingItemView( HomeCreatorRankingItemView(
@@ -205,7 +205,7 @@ struct HomeTabView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<viewModel.originalAudioDramaList.count, id: \.self) { ForEach(0..<viewModel.originalAudioDramaList.count, id: \.self) {
SeriesItemView(item: viewModel.originalAudioDramaList[$0]) SeriesItemView(item: viewModel.originalAudioDramaList[$0])
} }
@@ -269,7 +269,7 @@ struct HomeTabView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<viewModel.recommendChannelList.count, id: \.self) { ForEach(0..<viewModel.recommendChannelList.count, id: \.self) {
RecommendChannelItemView(item: viewModel.recommendChannelList[$0]) RecommendChannelItemView(item: viewModel.recommendChannelList[$0])
} }
@@ -293,7 +293,7 @@ struct HomeTabView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<viewModel.freeContentList.count, id: \.self) { index in ForEach(0..<viewModel.freeContentList.count, id: \.self) { index in
ContentItemView(item: viewModel.freeContentList[index]) ContentItemView(item: viewModel.freeContentList[index])
} }
@@ -315,7 +315,7 @@ struct HomeTabView: View {
.padding(.horizontal, 24) .padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) { LazyHStack(spacing: 16) {
ForEach(0..<curation.items.count, id: \.self) { index in ForEach(0..<curation.items.count, id: \.self) { index in
let item = curation.items[index] let item = curation.items[index]
ContentItemView( ContentItemView(

View File

@@ -21,11 +21,11 @@ struct HomeWeeklyChartItemView: View {
.foregroundColor(Color(hex: "B5E7FA")) .foregroundColor(Color(hex: "B5E7FA"))
.frame(width: 30) .frame(width: 30)
KFImage(URL(string: content.coverImageUrl)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: content.coverImageUrl),
.resizable() size: CGSize(width: 60, height: 60)
.frame(width: 60, height: 60) )
.cornerRadius(12) .cornerRadius(12)
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text(content.title) Text(content.title)

View File

@@ -16,11 +16,11 @@ struct RecommendChannelContentItemView: View {
var body: some View { var body: some View {
HStack(spacing: 16) { HStack(spacing: 16) {
KFImage(URL(string: item.thumbnailImageUrl)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: item.thumbnailImageUrl),
.resizable() size: CGSize(width: 60, height: 60)
.frame(width: 60, height: 60) )
.cornerRadius(12) .cornerRadius(12)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(item.title) Text(item.title)

View File

@@ -17,12 +17,11 @@ struct RecommendChannelItemView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 6) { HStack(spacing: 6) {
KFImage(URL(string: item.creatorProfileImageUrl)) DownsampledKFImage(
.cancelOnDisappear(true) url: URL(string: item.creatorProfileImageUrl),
.resizable() size: CGSize(width: 80, height: 80)
.aspectRatio(contentMode: .fill) )
.frame(width: 80, height: 80, alignment: .top) .clipShape(Circle())
.clipShape(Circle())
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(item.creatorNickname) Text(item.creatorNickname)