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
4 changes: 4 additions & 0 deletions AsyncSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
C6F7798F28C9D1BF0036773B /* SessionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F7798E28C9D1BF0036773B /* SessionView.swift */; };
C6F7799128C9E5DD0036773B /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F7799028C9E5DD0036773B /* EventDetailView.swift */; };
E9171F0028D15426002FAF52 /* TicketingView+Observed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9171EFF28D15426002FAF52 /* TicketingView+Observed.swift */; };
E94F92C728D2505100D9E759 /* Ticketing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94F92C628D2505100D9E759 /* Ticketing.swift */; };
E9E2A4D828CEC5680016AEFF /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E2A4D728CEC5680016AEFF /* WebView.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -60,6 +61,7 @@
C6F7798E28C9D1BF0036773B /* SessionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionView.swift; sourceTree = "<group>"; };
C6F7799028C9E5DD0036773B /* EventDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailView.swift; sourceTree = "<group>"; };
E9171EFF28D15426002FAF52 /* TicketingView+Observed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TicketingView+Observed.swift"; sourceTree = "<group>"; };
E94F92C628D2505100D9E759 /* Ticketing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ticketing.swift; sourceTree = "<group>"; };
E9E2A4D728CEC5680016AEFF /* WebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -147,6 +149,7 @@
children = (
C66C68D228D1B00A0091F960 /* EventModel.swift */,
C66C68D428D1B0130091F960 /* SessionModel.swift */,
E94F92C628D2505100D9E759 /* Ticketing.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -243,6 +246,7 @@
buildActionMask = 2147483647;
files = (
C68DE94C28C76F3200CA4CC8 /* AppDelegate.swift in Sources */,
E94F92C728D2505100D9E759 /* Ticketing.swift in Sources */,
C66DAD5028CF478700195DEB /* SessionView+Observed.swift in Sources */,
C6E744A028CA557100B7B2BD /* Color+.swift in Sources */,
C68DE95128C77DDA00CA4CC8 /* TicketingView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.920",
"blue" : "0xEA",
"green" : "0xE5",
"red" : "0xE5"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
15 changes: 0 additions & 15 deletions AsyncSwift/Assets.xcassets/Seminar002Ticket.imageset/Contents.json

This file was deleted.

Binary file not shown.

This file was deleted.

Binary file not shown.
1 change: 1 addition & 0 deletions AsyncSwift/Extensions/Color+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ extension Color {
static let seminarOrange = Color("seminarOrange")
static let dividerForeground = Color("dividerForeground")
static let speakerBackground = Color("speakerBackground")
static let skeletonBackground = Color("skeletonBackground")
}
50 changes: 50 additions & 0 deletions AsyncSwift/Models/Ticketing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// Ticketing.swift
// AsyncSwift
//
// Created by Eunyeong Kim on 2022/09/15.
//

import SwiftUI

struct Ticketing: Decodable {
let currentTicket: CurrentTicket?
let upcomingEvent: UpcomingEvent?

struct CurrentTicket: Decodable {
let ticketingImageURL: String
let ticketingURL: String
}

struct UpcomingEvent: Decodable {
let headerTitle: String
let title: String
let subscription: String
private let gradientStartColor: RGBColor
private let gradientEndColor: RGBColor

var backgroundGradientStartColor: Color {
makeColor(from: gradientStartColor)
}

var backgroundGradientEndColor: Color {
makeColor(from: gradientEndColor)
}

private func makeColor(from rgbColor: RGBColor) -> Color {
Color(
red: rgbColor.red / 255.0,
green: rgbColor.green / 255.0,
blue: rgbColor.blue / 255.0,
opacity: rgbColor.opacity / 100.0
)
}

struct RGBColor: Decodable {
let red: Double
let green: Double
let blue: Double
let opacity: Double
}
}
}
34 changes: 32 additions & 2 deletions AsyncSwift/Observed/TicketingView+Observed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,43 @@
//

import Combine
import Foundation

extension TicketingView {

final class Observed: ObservableObject {
@Published var ticketing: Ticketing?

@Published var isActivatedWebViewNavigationLink = false

let upcomingEventURL = "https://www.eventbrite.com/e/asyncswift-seminar-002-tickets-408509251167"
var isNeedToShowTicketingView: Bool { ticketing?.currentTicket?.ticketingImageURL != nil }
var isTicketingLinkDisabled: Bool { ticketing?.currentTicket?.ticketingURL == nil }

func onAppear() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

여기서 사용되는 fetch 메소드들은 @insub4067 의 fetch 코드와 일원화 되면 어떨까하네요 ..!

Copy link
Member

Choose a reason for hiding this comment

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

그러면 NetworkManager 등으로 따로 fetch method를 캡슐화 해줄까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

말씀드리지 않아도 다 알아주시다니 ..!!!!!! 😭

Copy link
Member

@insub4067 insub4067 Sep 14, 2022

Choose a reason for hiding this comment

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

고민이 생겼습니다

영이 fetch를 통해 json에서 받아오고자 하는 data 의 type 과 제가 사용하는 data의 type이 다르기 때문에

func 의 return 의 type을 구분하기 위해 거의 같지만 type 만 다른 2개의 함수를 작성해야 합니다

하지만 여전히 캡슐화의 의미는 있습니다 혹시 코드의 재사용성을 높이기 위해 더 좋은 아이디어 있을까요?

Copy link
Collaborator Author

@unnnyong unnnyong Sep 14, 2022

Choose a reason for hiding this comment

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

그건 Protocol, Generic, Async/Await 등을 활용하면 문제가 되지 않을 것 같아요 : )

이 수정은 Optional한 리팩토링 작업이라 지금당장은 우선이 되지않아도 되지않을까 생각되는데 @insub4067 은 어떠실까요 ?
제가 내일 정리해서 이야기드릴까합니다 🙆‍♂️

Copy link
Member

@insub4067 insub4067 Sep 14, 2022

Choose a reason for hiding this comment

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

json fetch가 한번이 이상 일어나기 때문에 구분하는게 맞다고 봅니다
내일 결론 내어 manager 로 refactoring 진행하는게 좋지 않나 싶습니다

Copy link
Member

Choose a reason for hiding this comment

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

음.. 그냥 return Any로 하는게 답일까 싶은데 내일 얘기 나눠보시죠

Copy link
Collaborator

@E-know E-know Sep 14, 2022

Choose a reason for hiding this comment

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

onAppear 라는 함수명이 모호하다고 생각합니다.

onViewAppear 처럼 어떤 객체의 변화인지를 쓰면 좋을 것 같습니다!

Copy link
Member

Choose a reason for hiding this comment

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

  • isTicketLinkeEnabled 로 변수명을 수정하는게 어떨까요?

Copy link
Collaborator Author

@unnnyong unnnyong Sep 14, 2022

Choose a reason for hiding this comment

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

#28 (comment)

d0ecaec 에서 수정하였습니다 🙆‍♂️

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#28 (comment)

@E-know

실제 뷰에서 실행되는 modifier와 통일되어야 동일한 타이밍에 호출된다는 것을 알기 쉽다고 생각되었어요 .. !
onViewAppear 로 되면 View에서 onAppear`와는 별도로 새로운 method가 있다고 생각될 것도 같네요

혹시 needToCallOnAppear와 같은 네이밍은 어떠신가요 ?

Copy link
Member

Choose a reason for hiding this comment

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

view 는 observed(viewModel) 에게 view 가 나타났다 (onAppear 실행됐다) 정도만 알리면 될거 같습니다
따라서 viewDidAppear 정도면 합리적인 네이밍이지 않을까요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

viewDidAppear는 UIKit 메소드와 같아서 사견으로 SwiftUI에서는 지양하고 싶네요

Copy link
Member

Choose a reason for hiding this comment

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

SwiftUI의 역습 (?) 🚀

Copy link
Member

Choose a reason for hiding this comment

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

view 떼고 didAppear 어떠세요 그럼?

Copy link
Collaborator

Choose a reason for hiding this comment

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

그럼 작동하는 객체가 빠져 있어서 오히려 애매한?!?!

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. readyViewToAppear
  2. whenViewAppear

작명소 OPEN!

guard
let url = URL(string: "https://raw.githubusercontent.com/Async-Swift/jsonstorage/main/ticketing.json")
else { return }


let request = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: request) { data, response, _ in
guard
let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data
else { return }

DispatchQueue.main.async { [weak self] in
do {
let ticketing = try JSONDecoder().decode(Ticketing.self, from: data)
self?.ticketing = ticketing
} catch {
self?.ticketing = nil
}
}
}

dataTask.resume()
}

func didTappedTicketingButton() {
isActivatedWebViewNavigationLink = true
Expand Down
111 changes: 92 additions & 19 deletions AsyncSwift/Views/TicketingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,105 @@ import SwiftUI
struct TicketingView: View {
@StateObject private var observed = Observed()

private let shadowColor = Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.15)
private let corenrRadius: CGFloat = 8.0

var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 30) {
NavigationLink(
isActive: $observed.isActivatedWebViewNavigationLink) {
WebView(url: observed.upcomingEventURL)
} label: {
Image("Seminar002Ticket")
.resizable()
.aspectRatio(contentMode: .fit)
.shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.15), radius: 20, x: 0, y: 4)
}

Image("UpcomingConference001")
.resizable()
.aspectRatio(contentMode: .fit)
if observed.isNeedToShowTicketingView {
ticketingView
}

if let upcomingEvent = observed.ticketing?.upcomingEvent {
makeUpcomingEventView(from: upcomingEvent)
} else {
skeletonView
.aspectRatio(2.75, contentMode: .fill)
.cornerRadius(corenrRadius)
}
}
.aspectRatio(contentMode: .fit)
.padding(30)

}
.navigationTitle("Ticketing")
} // NavigationView
} // body
} // View
}
.navigationTitle("Ticketing")
}.onAppear {
observed.onAppear()
}
}
}

private extension TicketingView {
var skeletonView: some View {
LinearGradient(
colors: [.skeletonBackground, .white],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.animation(.linear(duration: 3.0), value: 1.0)
}

var ticketingView: some View {
NavigationLink(
isActive: $observed.isActivatedWebViewNavigationLink
) {
if let upcomingEventURL = observed.ticketing?.currentTicket?.ticketingURL {
WebView(url: upcomingEventURL)
}
} label: {
AsyncImage(
url: URL(string: observed.ticketing?.currentTicket?.ticketingImageURL ?? ""),
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.transition(AnyTransition.opacity.animation(.easeInOut))
},
placeholder: {
skeletonView
.aspectRatio(0.85, contentMode: .fill)
}
)
.cornerRadius(corenrRadius)
.shadow(color: shadowColor, radius: 20, x: 0, y: 4)
}.disabled(observed.isTicketingLinkDisabled)
}

@ViewBuilder
func makeUpcomingEventView(from upcomingEvent: Ticketing.UpcomingEvent) -> some View {
HStack(alignment: .top, spacing: 0.0) {
VStack(alignment: .leading, spacing: 15.0) {
Text(upcomingEvent.headerTitle)
.fontWeight(.bold)
.font(.caption2)
VStack(alignment: .leading, spacing: 5.0) {
Text(upcomingEvent.title)
.fontWeight(.bold)
.font(.title2)
Text(upcomingEvent.subscription)
.fontWeight(.bold)
.font(.subheadline)
}
}
Spacer()
}
.frame(maxWidth: .infinity)
.padding(
EdgeInsets(top: 15.0, leading: 12.0, bottom: 24.0, trailing: 12.0)
)
.foregroundColor(.white)
.background(
LinearGradient(
colors: [upcomingEvent.backgroundGradientStartColor, upcomingEvent.backgroundGradientEndColor],
startPoint: .leading,
endPoint: .trailing
)
)
.cornerRadius(corenrRadius)
.shadow(color: shadowColor, radius: 20, x: 0, y: 4)
}
}

struct TicketView_Previews: PreviewProvider {
static var previews: some View {
Expand Down