import SwiftUI struct MainHomeGenreCreatorSection: View { let items: [HomeGenreCreatorGroupItem] let isFollowAllCompleted: (String) -> Bool let isFollowAllLoading: (String) -> Bool let onTapCreator: (Int) -> Void let onTapFollowAll: ([Int], String) -> Void @State private var containerWidth = UIScreen.main.bounds.width init( items: [HomeGenreCreatorGroupItem], isFollowAllCompleted: @escaping (String) -> Bool = { _ in false }, isFollowAllLoading: @escaping (String) -> Bool = { _ in false }, onTapCreator: @escaping (Int) -> Void = { _ in }, onTapFollowAll: @escaping ([Int], String) -> Void = { _, _ in } ) { self.items = items self.isFollowAllCompleted = isFollowAllCompleted self.isFollowAllLoading = isFollowAllLoading self.onTapCreator = onTapCreator self.onTapFollowAll = onTapFollowAll } var body: some View { if !items.isEmpty { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(alignment: .top, spacing: SodaSpacing.s4) { ForEach(Array(items.enumerated()), id: \.offset) { index, item in let followKey = "genre-\(index)-\(item.genreName)" MainHomeCreatorGroupSection( title: item.genreName, creators: item.creators, isFollowAllCompleted: isFollowAllCompleted(followKey), isFollowAllLoading: isFollowAllLoading(followKey), onTapCreator: onTapCreator, onTapFollowAll: { onTapFollowAll(item.creators.map(\.creatorId), followKey) } ) .frame(width: cardWidth) } } .padding(.leading, SodaSpacing.s14) .padding(.trailing, SodaSpacing.s20) } .background(widthReader) } } private var cardWidth: CGFloat { max(0, containerWidth - (SodaSpacing.s14 * 2)) } private var widthReader: some View { GeometryReader { proxy in Color.clear .onAppear { updateContainerWidth(proxy.size.width) } .onChange(of: proxy.size.width) { newWidth in updateContainerWidth(newWidth) } } } private func updateContainerWidth(_ width: CGFloat) { guard width > 0, abs(containerWidth - width) > 0.5 else { return } containerWidth = width } } struct MainHomeCreatorGroupSection: View { private static let maxProfileSize: CGFloat = 75 let title: String let showTitle: Bool let creators: [HomeCreatorItem] let isFollowAllCompleted: Bool let isFollowAllLoading: Bool let onTapCreator: (Int) -> Void let onTapFollowAll: () -> Void @State private var cardWidth = UIScreen.main.bounds.width - (SodaSpacing.s14 * 2) init( title: String, showTitle: Bool = true, creators: [HomeCreatorItem], isFollowAllCompleted: Bool = false, isFollowAllLoading: Bool = false, onTapCreator: @escaping (Int) -> Void = { _ in }, onTapFollowAll: @escaping () -> Void = {} ) { self.title = title self.showTitle = showTitle self.creators = creators self.isFollowAllCompleted = isFollowAllCompleted self.isFollowAllLoading = isFollowAllLoading self.onTapCreator = onTapCreator self.onTapFollowAll = onTapFollowAll } var body: some View { let profileSize = profileSize(for: cardWidth) VStack(alignment: .leading, spacing: SodaSpacing.s14) { if showTitle { Text(title) .appFont(.heading3) .foregroundColor(.white) .lineLimit(1) .truncationMode(.tail) } LazyVGrid(columns: columns, alignment: .center, spacing: SodaSpacing.s14) { ForEach(creators.prefix(8), id: \.self) { creator in CreatorProfileItem( imageUrl: creator.creatorProfileImage, name: creator.creatorNickname, imageSize: CGSize(width: profileSize, height: profileSize), spacing: SodaSpacing.s6 ) { onTapCreator(creator.creatorId) } .frame(width: profileSize) } } FollowAllButton( isCompleted: isFollowAllCompleted, isLoading: isFollowAllLoading, action: onTapFollowAll ) } .padding(SodaSpacing.s14) .frame(maxWidth: .infinity, alignment: .leading) .background(cardBackground) .background(widthReader) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) } private var columns: [GridItem] { Array(repeating: GridItem(.fixed(profileSize(for: cardWidth)), spacing: SodaSpacing.s14), count: 4) } private var widthReader: some View { GeometryReader { proxy in Color.clear .onAppear { updateCardWidth(proxy.size.width) } .onChange(of: proxy.size.width) { newWidth in updateCardWidth(newWidth) } } } private func updateCardWidth(_ width: CGFloat) { guard width > 0, abs(cardWidth - width) > 0.5 else { return } cardWidth = width } private func profileSize(for width: CGFloat) -> CGFloat { let contentWidth = width - (SodaSpacing.s14 * 2) let spacingWidth = SodaSpacing.s14 * 3 let availableItemWidth = (contentWidth - spacingWidth) / 4 return min(Self.maxProfileSize, max(0, availableItemWidth.rounded(.down))) } private var cardBackground: some View { LinearGradient( stops: [ .init(color: Color.gray900, location: 0), .init(color: Color.gray900, location: 0.29327), .init(color: Color.soda700, location: 1) ], startPoint: .top, endPoint: .bottom ) } } struct MainHomeGenreCreatorSection_Previews: PreviewProvider { private static let previewImageUrl = "https://picsum.photos/200/200" static var previews: some View { MainHomeGenreCreatorSection( items: [ HomeGenreCreatorGroupItem( genreName: I18n.HomeRecommendation.genreCreatorSectionTitle, creators: (1...8).map { HomeCreatorItem( creatorId: $0, creatorNickname: "크리에이터이름", creatorProfileImage: previewImageUrl ) } ) ] ) .padding(.vertical, SodaSpacing.s20) .background(Color.black) .previewLayout(.sizeThatFits) } }