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
14 changes: 4 additions & 10 deletions AsyncSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -515,12 +515,10 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AsyncSwift/AsyncSwift.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"AsyncSwift/Preview Content\"";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 76AJ433CP5;
DEVELOPMENT_TEAM = 76AJ433CP5;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AsyncSwift/Info.plist;
Expand All @@ -543,7 +541,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.kim.AsyncSwift;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = AsyncSwiftProvisioning;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
Expand All @@ -560,12 +557,10 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AsyncSwift/AsyncSwift.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"AsyncSwift/Preview Content\"";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 76AJ433CP5;
DEVELOPMENT_TEAM = 76AJ433CP5;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AsyncSwift/Info.plist;
Expand All @@ -588,7 +583,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.kim.AsyncSwift;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = AsyncSwiftProvisioning;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
Expand Down
3 changes: 2 additions & 1 deletion AsyncSwift/Models/Stamp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ struct Stamp: Decodable {
}

struct Card {

var originalPosition: CGFloat
var isSelected = false
var image: Image
var event: String
}
69 changes: 20 additions & 49 deletions AsyncSwift/Observed/StampView+Observed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import SwiftUI

extension StampView {
@MainActor final class Observed: ObservableObject {
@Published var cards = [String: Card]()
@Published var cards: [Card] = []
@Published var events = [String]()
@Published var currentIndex = 0
@Published var isLoading = true
private let keyChainManager = KeyChainManager()
private let cardInterval: CGFloat = (UIScreen.main.bounds.width - 32) * 56 / 358
private let cardSize: CGFloat = UIScreen.main.bounds.width - 32
Expand All @@ -22,8 +23,7 @@ extension StampView {

private func getEvents() -> [String] {
let pwRaw = keyChainManager.getItem(key: keyChainManager.stampKey) as? String
guard var convertedStringArray = pwRaw?.convertToStringArray() else { return [] }
convertedStringArray.insert("Next", at: 0) // MARK: Test 실제에서는 Next storage 둘다 설정해야함
guard let convertedStringArray = pwRaw?.convertToStringArray() else { return [] }
self.events = convertedStringArray.reversed()
return events
}
Expand All @@ -33,27 +33,33 @@ extension StampView {
private func fetchStampsImages(){
let events = getEvents()

guard !events.isEmpty else {
isLoading = false
return
}

events.enumerated().forEach { [weak self] in
guard let self = self else { return }
guard let self else { return }
let event = $0.element
let index = $0.offset
Task { @MainActor () -> Void in
guard let cardImageURL = URL(string: "https://raw.githubusercontent.com/Async-Swift/jsonstorage/main/Images/Stamp/" + event + "/stamp.png")
else { return }

let cardImageRequest = URLRequest(url: cardImageURL)
let (cardImageData, cardImageResponse) = try await URLSession.shared.data(for: cardImageRequest)
guard let httpsResponse = cardImageResponse as? HTTPURLResponse, httpsResponse.statusCode == 200 else { return }

guard let cardUIImage = UIImage(data: cardImageData) else { return }

self.cards[event] = Card(originalPosition: cardInterval * CGFloat(index),
image: Image(uiImage: cardUIImage))
// 가장 최근의 EventCard가 선택된 상태로 지정하기
if index == 0 {
self.cards[event]?.isSelected = true
} else {
self.cards[event]?.isSelected = false

let card = Card(
originalPosition: self.cardInterval * CGFloat(index),
image: Image(uiImage: cardUIImage),
event: event
)
self.cards.append(card)
if index == events.count - 1 {
self.isLoading = false
}
}
}
Expand Down Expand Up @@ -105,40 +111,5 @@ extension StampView {

return stamp
}

/// 가장 맨 위에 올라온 카드라면 아무것도 작동안하도록, 아니라면 가장 맨위로 올도록 하는 함수입니다.
func didCardTapped(index: Int, scrollReader: ScrollViewProxy) {
if index != currentIndex {
scrollReader.scrollTo(0, anchor: .init(x: 0, y: 94))
withAnimation(.spring()) {
cards[events[index]]?.isSelected = true
cards[events[currentIndex]]?.isSelected = false
currentIndex = index
}
}
}

/// 카드의 개수에 따라서 카드의 위치를 지정해주는 함수입니다.
func getCardOffsetY(index: Int, size: CGSize) -> CGFloat {
withAnimation(.spring()) {
guard let card = cards[events[index]] else { return .zero}
if card.isSelected {
return .zero
} else if size.height - CGFloat(94) < cardSize + CGFloat(16) + cardInterval * CGFloat(cards.count - 1) {
return cardSize + CGFloat(16) + card.originalPosition
} else {
return size.height - CGFloat(94) - cardInterval * CGFloat(cards.count - index) - CGFloat(8)
}
}
}

/// 스크롤을 원할하게 하기 위해서 Offset 으로 원래 크기 보다 밀려난 만큼 Spacer로 확보해줍니다.
func getSpacerMinLength(size: CGSize) -> CGFloat {
if size.height - CGFloat(94) < cardSize + CGFloat(16) + cardInterval * CGFloat(cards.count - 1) {
return cardSize + CGFloat(16) + cardInterval * CGFloat(cards.count - 1) + cardSize
} else {
return size.height - CGFloat(94) - cardInterval
}
}
}
}
71 changes: 35 additions & 36 deletions AsyncSwift/Views/StampView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,65 @@ import SwiftUI
struct StampView: View {
@StateObject var observed = Observed()
@EnvironmentObject var envObserved: MainTabViewObserved
let columns = [
GridItem(.flexible(), spacing: 10),
GridItem(.flexible())
]

var body: some View {
GeometryReader { geometry in
if observed.cards.isEmpty {
emptyCardView
.padding(36)
} else {

GeometryReader { proxy in
NavigationView {
ScrollView(showsIndicators: false) {
ScrollViewReader { reader in
HStack(alignment: .bottom) {
Text("Stamp")
.font(.system(size: 34))
.fontWeight(.bold)
.padding(.leading, 16)
.padding(.bottom, 7)
.padding(.top, 48)
Spacer()
}
.frame(height: 94)
ZStack {
ForEach(0..<observed.cards.count, id: \.self) { index in
cardView(index: index, size: geometry.size, scrollReader: reader)
LazyVGrid(
columns: columns,
spacing: 10
) {
ForEach(observed.cards, id: \.event) { card in
cardView(card: card, size: proxy.size)
}
.padding(.horizontal, 16)
}
.padding(.horizontal, 14)
}
Spacer(minLength: observed.getSpacerMinLength(size: geometry.size))
}

}
}
.onOpenURL{ url in
Task {
if await observed.isAvailableURL(url: url) {
envObserved.currentTab = .stamp
.navigationTitle(Tab.stamp.title)
.overlay {
if observed.isLoading {
loadingIndicator
} else if !observed.isLoading, observed.cards.isEmpty {
emptyCardView
.padding(36)
}
}
}
}
}
}

private extension StampView {
var emptyCardView: some View {

@ViewBuilder var loadingIndicator: some View {
ProgressView()
.scaleEffect(1.5)
.padding(30)
.background(.ultraThinMaterial)
.cornerRadius(10)
}

@ViewBuilder var emptyCardView: some View {
ZStack {
RoundedRectangle(cornerRadius: 30)
.strokeBorder(Color(red: 0.78, green: 0.78, blue: 0.8), style: StrokeStyle(lineWidth: 2, dash: [10]))

Text("아직 참여한 행사가 없습니다.")
.foregroundColor(.gray)
}
}

func cardView(index: Int, size: CGSize, scrollReader: ScrollViewProxy) -> some View {
observed.cards[observed.events[index]]?.image
@ViewBuilder func cardView(card: Card, size: CGSize) -> some View {
card.image
.resizable()
.aspectRatio(contentMode: .fit)
.aspectRatio(contentMode: .fill)
.shadow(color: Color.black.opacity(0.1), radius: 10, y: 4)
.offset(y: observed.getCardOffsetY(index: index, size: size))
.onTapGesture {
observed.didCardTapped(index: index, scrollReader: scrollReader)
}
}
}