diff --git a/week2/Memorize/Memorize.xcodeproj/project.pbxproj b/week2/Memorize/Memorize.xcodeproj/project.pbxproj index 9b226b4..4f77fbd 100644 --- a/week2/Memorize/Memorize.xcodeproj/project.pbxproj +++ b/week2/Memorize/Memorize.xcodeproj/project.pbxproj @@ -7,22 +7,21 @@ objects = { /* Begin PBXBuildFile section */ + A690699028F5413B0070676A /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A690698F28F5413B0070676A /* Extensions.swift */; }; A69F17AF288E87F600AF88CC /* MemorizeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17AE288E87F600AF88CC /* MemorizeApp.swift */; }; A69F17B1288E87F600AF88CC /* MemorizeGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17B0288E87F600AF88CC /* MemorizeGameView.swift */; }; A69F17B3288E87F700AF88CC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A69F17B2288E87F700AF88CC /* Assets.xcassets */; }; A69F17B6288E87F700AF88CC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A69F17B5288E87F700AF88CC /* Preview Assets.xcassets */; }; A69F17C5288F77E100AF88CC /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17C4288F77E100AF88CC /* Theme.swift */; }; A69F17CB288F8BC400AF88CC /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17CA288F8BC400AF88CC /* CardView.swift */; }; - A69F17CF288F9B6400AF88CC /* CardListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17CE288F9B6400AF88CC /* CardListView.swift */; }; A69F17D72890CBC700AF88CC /* MemorizeGameDealer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17D62890CBC700AF88CC /* MemorizeGameDealer.swift */; }; - A69F17E52898B11C00AF88CC /* ThemeNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17E42898B11C00AF88CC /* ThemeNameView.swift */; }; - A69F17E72898B26C00AF88CC /* NewGameButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17E62898B26C00AF88CC /* NewGameButton.swift */; }; A69F17E92898C1CD00AF88CC /* MemorizeGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17E82898C1CD00AF88CC /* MemorizeGame.swift */; }; - A69F17F1289A2D7A00AF88CC /* ScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17F0289A2D7A00AF88CC /* ScoreView.swift */; }; A69F17F7289B5A9300AF88CC /* SuccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69F17F6289B5A9300AF88CC /* SuccessView.swift */; }; + A6FD786128E40041009A2677 /* Cardify.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FD786028E40041009A2677 /* Cardify.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + A690698F28F5413B0070676A /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A69F17AB288E87F600AF88CC /* Memorize.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memorize.app; sourceTree = BUILT_PRODUCTS_DIR; }; A69F17AE288E87F600AF88CC /* MemorizeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemorizeApp.swift; sourceTree = ""; }; A69F17B0288E87F600AF88CC /* MemorizeGameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemorizeGameView.swift; sourceTree = ""; }; @@ -30,13 +29,10 @@ A69F17B5288E87F700AF88CC /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; A69F17C4288F77E100AF88CC /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A69F17CA288F8BC400AF88CC /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = ""; }; - A69F17CE288F9B6400AF88CC /* CardListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardListView.swift; sourceTree = ""; }; A69F17D62890CBC700AF88CC /* MemorizeGameDealer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemorizeGameDealer.swift; sourceTree = ""; }; - A69F17E42898B11C00AF88CC /* ThemeNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNameView.swift; sourceTree = ""; }; - A69F17E62898B26C00AF88CC /* NewGameButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGameButton.swift; sourceTree = ""; }; A69F17E82898C1CD00AF88CC /* MemorizeGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemorizeGame.swift; sourceTree = ""; }; - A69F17F0289A2D7A00AF88CC /* ScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoreView.swift; sourceTree = ""; }; A69F17F6289B5A9300AF88CC /* SuccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessView.swift; sourceTree = ""; }; + A6FD786028E40041009A2677 /* Cardify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cardify.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -75,6 +71,7 @@ A69F17C3288F77AC00AF88CC /* Model */, A69F17F8289FD3E600AF88CC /* View */, A69F17F9289FD43A00AF88CC /* ViewModel */, + A690698F28F5413B0070676A /* Extensions.swift */, ); path = Memorize; sourceTree = ""; @@ -101,11 +98,8 @@ children = ( A69F17B0288E87F600AF88CC /* MemorizeGameView.swift */, A69F17CA288F8BC400AF88CC /* CardView.swift */, - A69F17CE288F9B6400AF88CC /* CardListView.swift */, - A69F17E42898B11C00AF88CC /* ThemeNameView.swift */, - A69F17E62898B26C00AF88CC /* NewGameButton.swift */, - A69F17F0289A2D7A00AF88CC /* ScoreView.swift */, A69F17F6289B5A9300AF88CC /* SuccessView.swift */, + A6FD786028E40041009A2677 /* Cardify.swift */, ); path = View; sourceTree = ""; @@ -189,14 +183,12 @@ buildActionMask = 2147483647; files = ( A69F17E92898C1CD00AF88CC /* MemorizeGame.swift in Sources */, + A690699028F5413B0070676A /* Extensions.swift in Sources */, A69F17C5288F77E100AF88CC /* Theme.swift in Sources */, A69F17B1288E87F600AF88CC /* MemorizeGameView.swift in Sources */, A69F17F7289B5A9300AF88CC /* SuccessView.swift in Sources */, - A69F17E72898B26C00AF88CC /* NewGameButton.swift in Sources */, - A69F17E52898B11C00AF88CC /* ThemeNameView.swift in Sources */, + A6FD786128E40041009A2677 /* Cardify.swift in Sources */, A69F17AF288E87F600AF88CC /* MemorizeApp.swift in Sources */, - A69F17F1289A2D7A00AF88CC /* ScoreView.swift in Sources */, - A69F17CF288F9B6400AF88CC /* CardListView.swift in Sources */, A69F17CB288F8BC400AF88CC /* CardView.swift in Sources */, A69F17D72890CBC700AF88CC /* MemorizeGameDealer.swift in Sources */, ); diff --git a/week2/Memorize/Memorize/Extensions.swift b/week2/Memorize/Memorize/Extensions.swift new file mode 100644 index 0000000..a206cb3 --- /dev/null +++ b/week2/Memorize/Memorize/Extensions.swift @@ -0,0 +1,22 @@ +// +// Extensions.swift +// Memorize +// +// Created by ohhyeongseok on 2022/10/11. +// + +import Foundation + +extension Array { + + var oneAndOnly: Element? { + count == 1 ? first : nil + } +} + +extension Array where Element: Identifiable { + + func index(matching element: Element) -> Index? { + firstIndex(where: { $0.id == element.id }) + } +} diff --git a/week2/Memorize/Memorize/Model/MemorizeGame.swift b/week2/Memorize/Memorize/Model/MemorizeGame.swift index 9b4cf80..876e907 100644 --- a/week2/Memorize/Memorize/Model/MemorizeGame.swift +++ b/week2/Memorize/Memorize/Model/MemorizeGame.swift @@ -1,6 +1,9 @@ import Foundation struct MemorizeGame { + + // MARK: Property(ies) + private(set) var cards: [Card] = [] private(set) var score = 0 private var currentOpenedCards: [Card] = [] @@ -8,10 +11,14 @@ struct MemorizeGame { cards.filter({ $0.isMatched }).count / 2 == cards.count / 2 } + // MARK: Initializer(s) + init(numberOfPairsOfCards: Int, createContent: (Int) -> CardContent) { cards = makeCards(numberOfPairsOfCards: numberOfPairsOfCards, createContent: createContent) } + // MARK: Method(s) + func makeCards(numberOfPairsOfCards: Int, createContent: (Int) -> CardContent) -> [Card] { var cards: [Card] = [] for pairIndex in 0 ..< numberOfPairsOfCards { @@ -61,8 +68,7 @@ struct MemorizeGame { cards[chosenIndex].isMatched = true } } - } - else { + } else { currentOpenedCards.forEach { opendCard in if opendCard.isFaceUpAtLeastOnce == true { score -= 1 @@ -74,8 +80,12 @@ struct MemorizeGame { } } + mutating func shuffle() { + cards.shuffle() + } + struct Card: Identifiable { - var isFaceUp = false + var isFaceUp = true var isMatched = false var isFaceUpAtLeastOnce = false let content: CardContent diff --git a/week2/Memorize/Memorize/View/CardListView.swift b/week2/Memorize/Memorize/View/CardListView.swift deleted file mode 100644 index 50525fd..0000000 --- a/week2/Memorize/Memorize/View/CardListView.swift +++ /dev/null @@ -1,15 +0,0 @@ -import SwiftUI - -struct CardListView: View { - @EnvironmentObject var memorizeGameDealer: MemorizeGameDealer - - var body: some View { - ScrollView { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]) { - ForEach(memorizeGameDealer.cards) { card in - CardView(card: card).aspectRatio(2 / 3, contentMode: .fit) - } - } - } - } -} diff --git a/week2/Memorize/Memorize/View/CardView.swift b/week2/Memorize/Memorize/View/CardView.swift index b334049..6498352 100644 --- a/week2/Memorize/Memorize/View/CardView.swift +++ b/week2/Memorize/Memorize/View/CardView.swift @@ -5,28 +5,23 @@ struct CardView: View { let card: MemorizeGame.Card var body: some View { - ZStack { - let shape = RoundedRectangle(cornerRadius: 20) - if card.isFaceUp { - shape.fill().foregroundColor(.white) - shape.strokeBorder(lineWidth: 5) - Text(card.content).font(.largeTitle) - } else if card.isMatched { - shape.fill(.white) - } else { - shape.fill().foregroundColor(memorizeGameDealer.themeColor) - } - } - .onTapGesture { - if !card.isMatched { - memorizeGameDealer.choose(card: card) - } - } + Text(card.content) + .font(.largeTitle) + .rotationEffect(Angle(degrees: card.isMatched ? 360 : 0)) + .animation(matchedRotateAnimation, value: card.isMatched) + .cardify(isFaceUp: card.isFaceUp, isMatched: card.isMatched) + } + + private var matchedRotateAnimation: Animation { + Animation + .linear(duration: AnimationConstants.matchedRotateDuration) + .repeatCount(AnimationConstants.matchedRotateCount, autoreverses: true) } } -struct CardView_Previews: PreviewProvider { - static var previews: some View { - CardView(card: MemorizeGame.Card(content: "aa", id: 1)) +extension CardView { + private enum AnimationConstants { + static let matchedRotateDuration = 5.0 + static let matchedRotateCount = 5 } } diff --git a/week2/Memorize/Memorize/View/Cardify.swift b/week2/Memorize/Memorize/View/Cardify.swift new file mode 100644 index 0000000..c40f059 --- /dev/null +++ b/week2/Memorize/Memorize/View/Cardify.swift @@ -0,0 +1,48 @@ +// +// Cardify.swift +// Memorize +// +// Created by ohhyeongseok on 2022/09/28. + +import SwiftUI + +struct Cardify: ViewModifier, Animatable { + @EnvironmentObject var memorizeGameDealer: MemorizeGameDealer + var isMatched: Bool + var rotation: Double + var animatableData: Double { + get { rotation } + set { rotation = newValue } + } + + init(isFaceUp: Bool, isMatched: Bool) { + rotation = isFaceUp ? 0 : 180 + self.isMatched = isMatched + } + + func body(content: Content) -> some View { + ZStack { + let shape = RoundedRectangle(cornerRadius: 20) + if rotation < 90 { + shape.fill().foregroundColor(.white) + shape.strokeBorder(lineWidth: 5) + } else if isMatched{ + shape.fill(.white) + } else { + shape.fill().foregroundColor(memorizeGameDealer.themeColor) + } + content.opacity(rotation < 90 ? 1 : 0) + } + .rotation3DEffect(Angle(degrees: rotation), axis: (0, 1, 0)) + + } +} + +extension View { + + func cardify(isFaceUp: Bool, isMatched: Bool) -> some View { + self.modifier(Cardify(isFaceUp: isFaceUp, isMatched: isMatched)) + } + +} + diff --git a/week2/Memorize/Memorize/View/MemorizeGameView.swift b/week2/Memorize/Memorize/View/MemorizeGameView.swift index 6da224e..95f2dbb 100644 --- a/week2/Memorize/Memorize/View/MemorizeGameView.swift +++ b/week2/Memorize/Memorize/View/MemorizeGameView.swift @@ -1,37 +1,155 @@ +// +// Cardify.swift +// Memorize +// +// Created by ohhyeongseok on 2022/09/28. + import SwiftUI struct MemorizeGameView: View { @EnvironmentObject var memorizeGameDealer: MemorizeGameDealer + @Namespace private var dealingNamespace + @State var dealt = Set() var body: some View { if memorizeGameDealer.isFinished { VStack { SuccessView(score: memorizeGameDealer.score) Spacer() - NewGameButton() + newGameButton } .foregroundColor(memorizeGameDealer.themeColor) } else { VStack { - ScrollView { + VStack { HStack { - ThemeNameView(currentThemeName: memorizeGameDealer.currentThemeName) - ScoreView(score: memorizeGameDealer.score) - .frame(width: 150, alignment: .trailing) + themeName + Spacer() + score } - CardListView() + cardList Spacer() } .padding() - NewGameButton() + HStack { + newGameButton + deckBody + shuffleButton + } } .foregroundColor(memorizeGameDealer.themeColor) } } + + // MARK: SubView(s) + + var newGameButton: some View { + Button(Texts.newGame) { + withAnimation(Animation.easeInOut(duration: AnimationConstants.dealDuration)) { + dealt.removeAll() + memorizeGameDealer.newGame() + } + } + .padding(DrawingConstants.padding) + } + + var score: some View { + Text("Score:\(memorizeGameDealer.score)").font(.title) + .padding(DrawingConstants.padding) + } + + var themeName: some View { + Text(memorizeGameDealer.currentThemeName).font(.largeTitle) + .padding(DrawingConstants.padding) + } + + var shuffleButton: some View { + Button(Texts.shuffle) { + withAnimation(Animation.easeInOut(duration: AnimationConstants.dealDuration)) { + memorizeGameDealer.shuffle() + } + } + .padding(DrawingConstants.padding) + } + + var cardList: some View { + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]) { + ForEach(memorizeGameDealer.cards) { card in + CardView(card: card).aspectRatio(DrawingConstants.cardAspectRatio, contentMode: .fit) + .matchedGeometryEffect(id: card.id, in: dealingNamespace) + .transition(.asymmetric(insertion: .slide, removal: .slide)) + .zIndex(zIndex(of: card)) + .onTapGesture { + withAnimation { + memorizeGameDealer.choose(card: card) + } + } + } + } + } + } + + var deckBody: some View { + ZStack { + ForEach(memorizeGameDealer.cards.filter(isUndealt)) { card in + CardView(card: card) + .matchedGeometryEffect(id: card.id, in: dealingNamespace) + .transition(.asymmetric(insertion: .scale, removal: .scale)) + .zIndex(zIndex(of: card)) + } + } + .frame(width: DrawingConstants.undealtWidth, height: DrawingConstants.undealtHeight) + .onTapGesture { + for card in memorizeGameDealer.cards { + withAnimation(dealAnimation(for: card)) { + deal(card) + } + } + } + } + + // MARK: Method(s) + + private func deal(_ card: MemorizeGame.Card) { + dealt.insert(card.id) + } + + private func isUndealt(_ card: MemorizeGame.Card) -> Bool { + dealt.contains(card.id) == false + } + + private func dealAnimation(for card: MemorizeGame.Card) -> Animation { + var delay = 0.0 + if let index = memorizeGameDealer.cards.index(matching: card) { + delay = Double(index) * (AnimationConstants.totalDealDuration / Double(memorizeGameDealer.cards.count)) + } + return Animation.easeInOut(duration: AnimationConstants.dealDuration).delay(delay) + } + + private func zIndex(of card: MemorizeGame.Card) -> Double { + -Double(memorizeGameDealer.cards.index(matching: card) ?? 0) + } } -struct MemorizeGameView_Previews: PreviewProvider { - static var previews: some View { - MemorizeGameView() +extension MemorizeGameView { + + private enum Texts { + static let newGame = "New Game" + static let shuffle = "Shuffle" + } + + private enum DrawingConstants { + static let cardAspectRatio: CGFloat = 2 / 3 + static let padding: CGFloat = 30 + + static let undealtHeight: CGFloat = 90 + static let undealtWidth = undealtHeight * cardAspectRatio + } + + private enum AnimationConstants { + static let dealDuration = 0.5 + static let totalDealDuration = 2.0 } } + diff --git a/week2/Memorize/Memorize/View/NewGameButton.swift b/week2/Memorize/Memorize/View/NewGameButton.swift deleted file mode 100644 index 907762a..0000000 --- a/week2/Memorize/Memorize/View/NewGameButton.swift +++ /dev/null @@ -1,17 +0,0 @@ -import SwiftUI - -struct NewGameButton: View { - @EnvironmentObject var memorizeGameDealer: MemorizeGameDealer - - var body: some View { - Button("New Game") { - memorizeGameDealer.newGame() - } - } -} - -struct NewStartButton_Previews: PreviewProvider { - static var previews: some View { - NewGameButton() - } -} diff --git a/week2/Memorize/Memorize/View/ScoreView.swift b/week2/Memorize/Memorize/View/ScoreView.swift deleted file mode 100644 index c6e28be..0000000 --- a/week2/Memorize/Memorize/View/ScoreView.swift +++ /dev/null @@ -1,15 +0,0 @@ -import SwiftUI - -struct ScoreView: View { - let score: Int - - var body: some View { - Text("Score:\(score)").font(.title) - } -} - -struct ScoreView_Previews: PreviewProvider { - static var previews: some View { - ScoreView(score: 1) - } -} diff --git a/week2/Memorize/Memorize/View/ThemeNameView.swift b/week2/Memorize/Memorize/View/ThemeNameView.swift deleted file mode 100644 index f4e95a5..0000000 --- a/week2/Memorize/Memorize/View/ThemeNameView.swift +++ /dev/null @@ -1,15 +0,0 @@ -import SwiftUI - -struct ThemeNameView: View { - let currentThemeName: String - - var body: some View { - Text(currentThemeName).font(.largeTitle) - } -} - -struct ThemeNameView_Previews: PreviewProvider { - static var previews: some View { - ThemeNameView(currentThemeName: "") - } -} diff --git a/week2/Memorize/Memorize/ViewModel/MemorizeGameDealer.swift b/week2/Memorize/Memorize/ViewModel/MemorizeGameDealer.swift index e8a8844..13236bc 100644 --- a/week2/Memorize/Memorize/ViewModel/MemorizeGameDealer.swift +++ b/week2/Memorize/Memorize/ViewModel/MemorizeGameDealer.swift @@ -65,4 +65,8 @@ class MemorizeGameDealer: ObservableObject { func choose(card: MemorizeGame.Card) { memorizeGameManager.choose(card: card) } + + func shuffle() { + memorizeGameManager.shuffle() + } } diff --git a/week4/Calculator/Calculator.xcodeproj/project.pbxproj b/week4/Calculator/Calculator.xcodeproj/project.pbxproj index e618279..3fccc2c 100644 --- a/week4/Calculator/Calculator.xcodeproj/project.pbxproj +++ b/week4/Calculator/Calculator.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ A67D5A3228AB573F00624B25 /* CalculatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A3128AB573F00624B25 /* CalculatorManager.swift */; }; A67D5A3428AB68B200624B25 /* PadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A3328AB68B200624B25 /* PadView.swift */; }; A67D5A3A28ADC86000624B25 /* ScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A3928ADC86000624B25 /* ScreenView.swift */; }; - A67D5A3C28ADCA6C00624B25 /* EachButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A3B28ADCA6C00624B25 /* EachButton.swift */; }; + A67D5A3C28ADCA6C00624B25 /* PadButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A3B28ADCA6C00624B25 /* PadButton.swift */; }; A67D5A3E28ADD28D00624B25 /* ButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A3D28ADD28D00624B25 /* ButtonStyle.swift */; }; A67D5A5628B7024800624B25 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A5528B7024800624B25 /* String+Extension.swift */; }; A67D5A5928B74B3200624B25 /* Numeric+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A5828B74B3200624B25 /* Numeric+Extension.swift */; }; @@ -32,7 +32,7 @@ A67D5A3128AB573F00624B25 /* CalculatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculatorManager.swift; sourceTree = ""; }; A67D5A3328AB68B200624B25 /* PadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadView.swift; sourceTree = ""; }; A67D5A3928ADC86000624B25 /* ScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenView.swift; sourceTree = ""; }; - A67D5A3B28ADCA6C00624B25 /* EachButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EachButton.swift; sourceTree = ""; }; + A67D5A3B28ADCA6C00624B25 /* PadButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadButton.swift; sourceTree = ""; }; A67D5A3D28ADD28D00624B25 /* ButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyle.swift; sourceTree = ""; }; A67D5A5528B7024800624B25 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; A67D5A5828B74B3200624B25 /* Numeric+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Numeric+Extension.swift"; sourceTree = ""; }; @@ -103,7 +103,7 @@ A67D5A2028AB566600624B25 /* CalculatorView.swift */, A67D5A3328AB68B200624B25 /* PadView.swift */, A67D5A3928ADC86000624B25 /* ScreenView.swift */, - A67D5A3B28ADCA6C00624B25 /* EachButton.swift */, + A67D5A3B28ADCA6C00624B25 /* PadButton.swift */, ); path = View; sourceTree = ""; @@ -206,7 +206,7 @@ files = ( A67D5A5628B7024800624B25 /* String+Extension.swift in Sources */, A67D5A5928B74B3200624B25 /* Numeric+Extension.swift in Sources */, - A67D5A3C28ADCA6C00624B25 /* EachButton.swift in Sources */, + A67D5A3C28ADCA6C00624B25 /* PadButton.swift in Sources */, A67D5A3E28ADD28D00624B25 /* ButtonStyle.swift in Sources */, A67D5A3428AB68B200624B25 /* PadView.swift in Sources */, A67D5A2128AB566600624B25 /* CalculatorView.swift in Sources */, diff --git a/week4/Calculator/Calculator/Extensions/Numeric+Extension.swift b/week4/Calculator/Calculator/Extensions/Numeric+Extension.swift index 2a3d52e..c5ce6f7 100644 --- a/week4/Calculator/Calculator/Extensions/Numeric+Extension.swift +++ b/week4/Calculator/Calculator/Extensions/Numeric+Extension.swift @@ -9,6 +9,9 @@ import Foundation extension Numeric { var exponentialNotation: String { - return Formatter.exponentialNotation.string(for: self) ?? "" + guard let exponentialNotationString = Formatter.exponentialNotation.string(for: self) else { + return "오류" + } + return exponentialNotationString } } diff --git a/week4/Calculator/Calculator/Extensions/String+Extension.swift b/week4/Calculator/Calculator/Extensions/String+Extension.swift index d40c973..9581101 100644 --- a/week4/Calculator/Calculator/Extensions/String+Extension.swift +++ b/week4/Calculator/Calculator/Extensions/String+Extension.swift @@ -8,21 +8,17 @@ import Foundation extension String { - var insertComma: String { + var decimalFormat: String { let numberFormatter = NumberFormatter(); numberFormatter.numberStyle = .decimal - if self.contains(".") { - let numberArray = self.components(separatedBy: ".") - let numberString = numberArray.first ?? "0" - guard let doubleValue = Double(numberString) else { - return self - } - return (numberFormatter.string(from: NSNumber(value: doubleValue)) ?? numberString) + ".\(numberArray[numberArray.index(numberArray.startIndex, offsetBy: 1)])" - } else { - guard let doubleValue = Double(self) else { - return self - } - return numberFormatter.string(from: NSNumber(value: doubleValue)) ?? self + let decimalFractionComponents = components(separatedBy: ".") + guard let decimalString = decimalFractionComponents.first, + let decimal = Int(decimalString), + var decimalFormat = numberFormatter.string(from: NSNumber(value: decimal)) + else { return "오류"} + if 1 < decimalFractionComponents.count, let fractionString = decimalFractionComponents.last { + decimalFormat += ".\(fractionString)" } + return decimalFormat } } diff --git a/week4/Calculator/Calculator/Model/Calculator.swift b/week4/Calculator/Calculator/Model/Calculator.swift index e4311f5..cc66288 100644 --- a/week4/Calculator/Calculator/Model/Calculator.swift +++ b/week4/Calculator/Calculator/Model/Calculator.swift @@ -9,18 +9,14 @@ import Foundation struct Calculator { - // MARK: Alias(es) - - typealias BinaryOperator = CalculatorManager.BinaryOperator - typealias Digit = CalculatorManager.Digit - // MARK: Propery(ies) private(set) var displayValue = "0" private(set) var isAllClear = true private var calculationResult: Decimal? = 0 private var newValue: Decimal? = nil - private var operation: BinaryOperator? = .add + private var `operator`: BinaryOperator? = .add + private var isPreOperatorEqual = false private var isCalculationResultIsNil: Bool { calculationResult == nil } @@ -33,7 +29,8 @@ struct Calculator { self.newValue = Decimal(string: displayValue) isAllClear = false } else { - if String(describing: self.newValue ?? 0).count >= 9 { + let maxLengthOfInputDigit = displayValue.contains(".") ? 10 : 9 + if displayValue.count >= maxLengthOfInputDigit { return } displayValue = displayValue.appending(String(describing: newValue.rawValue)) @@ -41,36 +38,38 @@ struct Calculator { } } - mutating func setOperation(_ newOperation: BinaryOperator) { - guard let operation = operation, let newValue = newValue else { - self.operation = newOperation - return + mutating func setOperator(_ newOperator: BinaryOperator) { + if isPreOperatorEqual { + `operator` = nil + newValue = nil + isPreOperatorEqual = false } - calculationResult = calculate(operation, newValue) - guard let calculationResult = calculationResult else { - displayValue = "오류" - return - } - displayValue = String(describing: calculationResult) + proveAndCalculate(newOperator: newOperator) self.newValue = nil - self.operation = newOperation + self.`operator` = newOperator } mutating func equal() { - guard let operation = operation, let newValue = newValue else { + proveAndCalculate(newOperator: nil) + } + + private mutating func proveAndCalculate(newOperator: BinaryOperator?) { + let isEqual = newOperator == nil + guard let `operator` = `operator`, let newValue = newValue else { return } - calculationResult = calculate(operation, newValue) + if isEqual { + isPreOperatorEqual = true + } + calculationResult = calculate(`operator`, newValue) guard let calculationResult = calculationResult else { displayValue = "오류" self.newValue = nil return } displayValue = String(describing: calculationResult) - self.newValue = nil - self.operation = nil } - + mutating func dot() { if displayValue.contains(".") || isCalculationResultIsNil { return @@ -81,36 +80,32 @@ struct Calculator { } mutating func percent() { - guard let calculationResult = calculationResult else { - return - } - if let newValue = newValue { - displayValue = String(describing: newValue * 0.01) - self.newValue = Decimal(string: displayValue) - } else { - displayValue = String(describing: calculationResult * 0.01) - self.calculationResult = Decimal(string: displayValue) - } + percentOrToggleSignOfDisplayNumber(isToggle: false) + } + + mutating func toggleSignOfDisplayNumber() { + percentOrToggleSignOfDisplayNumber(isToggle: true) } - mutating func toggle() { + private mutating func percentOrToggleSignOfDisplayNumber(isToggle: Bool) { guard let calculationResult = calculationResult else { return } - if let newValue = newValue { - displayValue = String(describing: -newValue) + let operand = isToggle ? -1 : 0.01 + if let newValue = newValue, !isPreOperatorEqual { + displayValue = String(describing: newValue * Decimal(operand)) self.newValue = Decimal(string: displayValue) } else { - displayValue = String(describing: -calculationResult) + displayValue = String(describing: calculationResult * Decimal(operand)) self.calculationResult = Decimal(string: displayValue) } } mutating func allClear() { - displayValue = "0" - newValue = nil + clear() + isPreOperatorEqual = false calculationResult = 0 - operation = .add + `operator` = .add } mutating func clear() { @@ -120,22 +115,21 @@ struct Calculator { } mutating func undoWhenDragged() { - if newValue == nil { - if displayValue.count > 1 { - displayValue.removeLast() - calculationResult = Decimal(string: displayValue) - } else if displayValue.count == 1 { - displayValue = "0" - calculationResult = Decimal(string: displayValue) - } + let isNewValueNil = newValue == nil + if displayValue.count == 1 { + displayValue = "0" + assignValueWhenUndo(isNewValueNil) } else { - if displayValue.count > 1 { - displayValue.removeLast() - newValue = Decimal(string: displayValue) - } else if displayValue.count == 1 { - displayValue = "0" - newValue = Decimal(string: displayValue) - } + displayValue.removeLast() + assignValueWhenUndo(isNewValueNil) + } + } + + private mutating func assignValueWhenUndo(_ newValueIsNil: Bool) { + if newValueIsNil { + calculationResult = Decimal(string: displayValue) + } else { + newValue = Decimal(string: displayValue) } } @@ -150,7 +144,7 @@ struct Calculator { return calculationResult - newValue case .divide: if newValue == 0 { - return nil + return nil } return calculationResult / newValue case .multiply: @@ -158,3 +152,74 @@ struct Calculator { } } } + +extension Calculator { + enum Button: Hashable { + case digit(_ digit: Digit) + case binaryOperator(_ binaryOperator: BinaryOperator) + case equal + case dot + case percent + case toggle + case allClear + case clear + + var appearance: String { + switch self { + case .digit(let digit): + return digit.appearance + case .binaryOperator(let binaryOperator): + return binaryOperator.appearance + case .equal: + return "=" + case .dot: + return "." + case .percent: + return "%" + case .toggle: + return "+/-" + case .allClear: + return "AC" + case .clear: + return "C" + } + } + } + + enum BinaryOperator { + case add + case substarct + case divide + case multiply + + var appearance: String { + switch self { + case .add: + return "+" + case .substarct: + return "-" + case .divide: + return "/" + case .multiply: + return "*" + } + } + } + + enum Digit: Int { + case zero + case one + case two + case three + case four + case five + case six + case seven + case eight + case nine + + var appearance: String { + return String(describing: self.rawValue) + } + } +} diff --git a/week4/Calculator/Calculator/Style/ButtonStyle.swift b/week4/Calculator/Calculator/Style/ButtonStyle.swift index 5e5dbee..4c76d21 100644 --- a/week4/Calculator/Calculator/Style/ButtonStyle.swift +++ b/week4/Calculator/Calculator/Style/ButtonStyle.swift @@ -7,36 +7,24 @@ import SwiftUI struct CalculateButtonStyle: ButtonStyle { - let buttonColor: (Color, Color) + let buttonColor: (background: Color, foreground: Color) let isZero: Bool - + func makeBody(configuration: Self.Configuration) -> some View { - if isZero { - configuration.label - .frame(maxWidth:UIScreen.main.bounds.size.width / 2, maxHeight: UIScreen.main.bounds.size.height / 12, alignment: .leading) - .font(.title) - .padding() - .background(buttonColor.0) - .foregroundColor(buttonColor.1) - .overlay { - if configuration.isPressed { - Color(white: 1.0, opacity: 0.3) - } - } - .clipShape(Capsule()) - } else { - configuration.label - .frame(maxWidth:UIScreen.main.bounds.size.width / 6.5, maxHeight: UIScreen.main.bounds.size.height / 12, alignment: .center) - .font(.title) - .padding() - .background(buttonColor.0) - .foregroundColor(buttonColor.1) - .overlay { - if configuration.isPressed { - Color(white: 1.0, opacity: 0.3) - } + configuration.label + .frame( + maxWidth:UIScreen.main.bounds.size.width / (isZero ? 2.0 : 6.5), + maxHeight: UIScreen.main.bounds.size.height / 12, + alignment: isZero ? .leading : .center) + .font(.title) + .padding() + .background(buttonColor.background) + .foregroundColor(buttonColor.foreground) + .overlay { + if configuration.isPressed { + Color(white: 1.0, opacity: 0.3) } - .clipShape(Circle()) - } + } + .clipShape(Capsule()) } } diff --git a/week4/Calculator/Calculator/View/CalculatorView.swift b/week4/Calculator/Calculator/View/CalculatorView.swift index 895118d..6b4b28a 100644 --- a/week4/Calculator/Calculator/View/CalculatorView.swift +++ b/week4/Calculator/Calculator/View/CalculatorView.swift @@ -9,18 +9,24 @@ import SwiftUI struct CalculatorView: View { @EnvironmentObject var calculatorManager: CalculatorManager - + + private func swipe() -> some Gesture { + DragGesture(minimumDistance: 10, coordinateSpace: .local) + .onEnded { drag in + if 0 < drag.translation.width { + calculatorManager.undoWhenSwiped() + } + } + } + var body: some View { VStack { Spacer() - ScreenView() + ScreenView(displayValue: calculatorManager.displayValue) + .contentShape(Rectangle()) + .gesture(swipe()) PadView() } - .contentShape(Rectangle()) - .gesture(DragGesture(minimumDistance: 10, coordinateSpace: .local) - .onEnded({ _ in - calculatorManager.undoWhenSwiped() - })) .background(.black) } } @@ -28,5 +34,6 @@ struct CalculatorView: View { struct CalculatorView_Previews: PreviewProvider { static var previews: some View { CalculatorView() + .environmentObject(CalculatorManager()) } } diff --git a/week4/Calculator/Calculator/View/EachButton.swift b/week4/Calculator/Calculator/View/EachButton.swift deleted file mode 100644 index 5d20c76..0000000 --- a/week4/Calculator/Calculator/View/EachButton.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Button.swift -// Calculator -// -// Created by ohhyeongseok on 2022/08/18. -// - -import SwiftUI - -struct EachButton: View { - @EnvironmentObject var calculatorManager: CalculatorManager - let button: CalculatorManager.Button - - var body: some View { - ZStack { - Button(button.appearance) { - calculatorManager.touchButton(button) - } .buttonStyle(CalculateButtonStyle(buttonColor: calculatorManager.buttonColor(button), isZero: button.appearance == "0")) - } - } -} - -//struct Button_Previews: PreviewProvider { -// static var previews: some View { -// Button() -// } -//} diff --git a/week4/Calculator/Calculator/View/PadButton.swift b/week4/Calculator/Calculator/View/PadButton.swift new file mode 100644 index 0000000..162a18e --- /dev/null +++ b/week4/Calculator/Calculator/View/PadButton.swift @@ -0,0 +1,33 @@ +// +// Button.swift +// Calculator +// +// Created by ohhyeongseok on 2022/08/18. +// + +import SwiftUI + +struct PadButton: View { + @EnvironmentObject var calculatorManager: CalculatorManager + let button: CalculatorManager.Button + + var body: some View { + ZStack { + Button(button.appearance) { + calculatorManager.touchButton(button) + } .buttonStyle( + CalculateButtonStyle( + buttonColor: calculatorManager.buttonColor(button), + isZero: button.appearance == "0" + ) + ) + } + } +} + +struct Button_Previews: PreviewProvider { + static var previews: some View { + PadButton(button: CalculatorManager.Button.allClear) + .environmentObject(CalculatorManager()) + } +} diff --git a/week4/Calculator/Calculator/View/PadView.swift b/week4/Calculator/Calculator/View/PadView.swift index 5baae41..a02d3af 100644 --- a/week4/Calculator/Calculator/View/PadView.swift +++ b/week4/Calculator/Calculator/View/PadView.swift @@ -8,14 +8,14 @@ import SwiftUI struct PadView: View { - @EnvironmentObject var calculatorManager: CalculatorManager + @EnvironmentObject var calculator: CalculatorManager var body: some View { VStack { - ForEach(calculatorManager.pad, id: \.self) { row in + ForEach(calculator.pad, id: \.self) { row in HStack { ForEach(row, id: \.self) { button in - EachButton(button: button) + PadButton(button: button) } } } @@ -26,5 +26,6 @@ struct PadView: View { struct PadView_Previews: PreviewProvider { static var previews: some View { PadView() + .environmentObject(CalculatorManager()) } } diff --git a/week4/Calculator/Calculator/View/ScreenView.swift b/week4/Calculator/Calculator/View/ScreenView.swift index 4e9d65c..8f88d8f 100644 --- a/week4/Calculator/Calculator/View/ScreenView.swift +++ b/week4/Calculator/Calculator/View/ScreenView.swift @@ -8,24 +8,34 @@ import SwiftUI struct ScreenView: View { - @EnvironmentObject var calculatorManager: CalculatorManager + let displayValue: String var body: some View { HStack { Spacer() - Text(calculatorManager.displayValue) - .lineLimit(1) + Text(displayValue) + .lineLimit(DrawConstants.lineLimit) .foregroundColor(.white) .frame(alignment: .bottomTrailing) - .font(.system(size: 80)) - .minimumScaleFactor(0.5) + .font(.system(size: DrawConstants.fontSize)) + .minimumScaleFactor(DrawConstants.minimumScaleFactor) .padding() } } } +// MARK: - Constant(s) + +extension ScreenView { + private enum DrawConstants { + static let lineLimit: Int = 1 + static let minimumScaleFactor: CGFloat = 0.5 + static let fontSize: CGFloat = 80 + } +} + struct ScreenView_Previews: PreviewProvider { static var previews: some View { - ScreenView() + ScreenView(displayValue: "aa") } } diff --git a/week4/Calculator/Calculator/ViewModel/CalculatorManager.swift b/week4/Calculator/Calculator/ViewModel/CalculatorManager.swift index ba3093b..08ddd01 100644 --- a/week4/Calculator/Calculator/ViewModel/CalculatorManager.swift +++ b/week4/Calculator/Calculator/ViewModel/CalculatorManager.swift @@ -5,23 +5,26 @@ // Created by ohhyeongseok on 2022/08/16. // -import Foundation import SwiftUI class CalculatorManager: ObservableObject { + // MARK: Alia(es) + + typealias Button = Calculator.Button + // MARK: Property(ies) @Published private var calculator = Calculator() - let maxNumberDisplayNormalNotation: Decimal = 999999999 var displayValue: String { guard let decimalTypeDisplayValue = Decimal(string: calculator.displayValue) else { return "오류" } - if decimalTypeDisplayValue > maxNumberDisplayNormalNotation { + let maxLengthOfDisplayValue = String(describing: calculator.displayValue).contains(".") ? 10 : 9 + if calculator.displayValue.count > maxLengthOfDisplayValue { return decimalTypeDisplayValue.exponentialNotation } - return calculator.displayValue.insertComma + return calculator.displayValue.decimalFormat } var pad: [[Button]] { var buttonLayout: [[Button]] = [ @@ -40,7 +43,14 @@ class CalculatorManager: ObservableObject { // MARK: Method(s) func buttonColor(_ button: Button) -> (Color, Color) { - return (button.backgroundColor, button.foregorundColor) + switch button { + case .digit, .dot: + return (Color(UIColor.darkGray), .white) + case .binaryOperator, .equal: + return (.orange, .white) + default: + return (Color(UIColor.lightGray), .black) + } } func undoWhenSwiped() { @@ -52,7 +62,7 @@ class CalculatorManager: ObservableObject { case .digit(let digit): calculator.setDigit(digit) case .binaryOperator(let binaryOperator): - calculator.setOperation(binaryOperator) + calculator.setOperator(binaryOperator) case .equal: calculator.equal() case .dot: @@ -60,7 +70,7 @@ class CalculatorManager: ObservableObject { case .percent: calculator.percent() case .toggle: - calculator.toggle() + calculator.toggleSignOfDisplayNumber() case .allClear: calculator.allClear() case .clear: @@ -68,94 +78,3 @@ class CalculatorManager: ObservableObject { } } } - -// MARK: Button - -extension CalculatorManager { - enum Button: Hashable { - case digit(_ digit: Digit) - case binaryOperator(_ binaryOperator: BinaryOperator) - case equal - case dot - case percent - case toggle - case allClear - case clear - - var appearance: String { - switch self { - case .digit(let digit): - return digit.appearance - case .binaryOperator(let binaryOperator): - return binaryOperator.appearance - case .equal: - return "=" - case .dot: - return "." - case .percent: - return "%" - case .toggle: - return "+/-" - case .allClear: - return "AC" - case .clear: - return "C" - } - } - var backgroundColor: Color { - switch self { - case .digit, .dot: - return Color(UIColor.darkGray) - case .binaryOperator, .equal: - return .orange - default: - return Color(UIColor.lightGray) - } - } - var foregorundColor: Color { - switch self { - case .digit, .dot, .binaryOperator, .equal: - return .white - default: - return .black - } - } - } - - enum BinaryOperator { - case add - case substarct - case divide - case multiply - - var appearance: String { - switch self { - case .add: - return "+" - case .substarct: - return "-" - case .divide: - return "/" - case .multiply: - return "*" - } - } - } - - enum Digit: Int { - case zero - case one - case two - case three - case four - case five - case six - case seven - case eight - case nine - - var appearance: String { - return String(describing: self.rawValue) - } - } -} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.pbxproj b/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.pbxproj new file mode 100644 index 0000000..efa5701 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.pbxproj @@ -0,0 +1,412 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + A67D5A7428C0537D00624B25 /* NaverOpenAPITestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A7328C0537D00624B25 /* NaverOpenAPITestApp.swift */; }; + A67D5A7828C0537F00624B25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A67D5A7728C0537F00624B25 /* Assets.xcassets */; }; + A67D5A7B28C0537F00624B25 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A67D5A7A28C0537F00624B25 /* Preview Assets.xcassets */; }; + A67D5A8C28C1B74000624B25 /* Finder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A8B28C1B74000624B25 /* Finder.swift */; }; + A67D5A8E28C1CD2E00624B25 /* NewsDesk.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A8D28C1CD2E00624B25 /* NewsDesk.swift */; }; + A67D5A9128C1CD6D00624B25 /* Encyclopedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A9028C1CD6D00624B25 /* Encyclopedia.swift */; }; + A67D5A9328C1DBCD00624B25 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A9228C1DBCD00624B25 /* String+Extension.swift */; }; + A67D5A9C28C1F51500624B25 /* ChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A9B28C1F51500624B25 /* ChoiceView.swift */; }; + A67D5A9E28C1F53100624B25 /* DocumentList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A9D28C1F53100624B25 /* DocumentList.swift */; }; + A67D5AA028C1F54900624B25 /* NewsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5A9F28C1F54900624B25 /* NewsList.swift */; }; + A67D5AA428C58D6F00624B25 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67D5AA328C58D6F00624B25 /* NetworkManager.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A67D5A7028C0537D00624B25 /* NaverOpenAPITest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NaverOpenAPITest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A67D5A7328C0537D00624B25 /* NaverOpenAPITestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NaverOpenAPITestApp.swift; sourceTree = ""; }; + A67D5A7728C0537F00624B25 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A67D5A7A28C0537F00624B25 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + A67D5A8B28C1B74000624B25 /* Finder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Finder.swift; sourceTree = ""; }; + A67D5A8D28C1CD2E00624B25 /* NewsDesk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsDesk.swift; sourceTree = ""; }; + A67D5A9028C1CD6D00624B25 /* Encyclopedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encyclopedia.swift; sourceTree = ""; }; + A67D5A9228C1DBCD00624B25 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; + A67D5A9B28C1F51500624B25 /* ChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChoiceView.swift; sourceTree = ""; }; + A67D5A9D28C1F53100624B25 /* DocumentList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentList.swift; sourceTree = ""; }; + A67D5A9F28C1F54900624B25 /* NewsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsList.swift; sourceTree = ""; }; + A67D5AA328C58D6F00624B25 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A67D5A6D28C0537D00624B25 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A67D5A6728C0537D00624B25 = { + isa = PBXGroup; + children = ( + A67D5A7228C0537D00624B25 /* NaverOpenAPITest */, + A67D5A7128C0537D00624B25 /* Products */, + ); + sourceTree = ""; + }; + A67D5A7128C0537D00624B25 /* Products */ = { + isa = PBXGroup; + children = ( + A67D5A7028C0537D00624B25 /* NaverOpenAPITest.app */, + ); + name = Products; + sourceTree = ""; + }; + A67D5A7228C0537D00624B25 /* NaverOpenAPITest */ = { + isa = PBXGroup; + children = ( + A67D5AA228C58D5100624B25 /* Network */, + A67D5A7328C0537D00624B25 /* NaverOpenAPITestApp.swift */, + A67D5A9428C1DBEC00624B25 /* Extension */, + A67D5A7728C0537F00624B25 /* Assets.xcassets */, + A67D5A7928C0537F00624B25 /* Preview Content */, + A67D5A8F28C1CD3800624B25 /* Model */, + A67D5A9A28C1F00D00624B25 /* View */, + A67D5A9928C1F00600624B25 /* ViewModel */, + ); + path = NaverOpenAPITest; + sourceTree = ""; + }; + A67D5A7928C0537F00624B25 /* Preview Content */ = { + isa = PBXGroup; + children = ( + A67D5A7A28C0537F00624B25 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + A67D5A8F28C1CD3800624B25 /* Model */ = { + isa = PBXGroup; + children = ( + A67D5A8D28C1CD2E00624B25 /* NewsDesk.swift */, + A67D5A9028C1CD6D00624B25 /* Encyclopedia.swift */, + ); + path = Model; + sourceTree = ""; + }; + A67D5A9428C1DBEC00624B25 /* Extension */ = { + isa = PBXGroup; + children = ( + A67D5A9228C1DBCD00624B25 /* String+Extension.swift */, + ); + path = Extension; + sourceTree = ""; + }; + A67D5A9928C1F00600624B25 /* ViewModel */ = { + isa = PBXGroup; + children = ( + A67D5A8B28C1B74000624B25 /* Finder.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + A67D5A9A28C1F00D00624B25 /* View */ = { + isa = PBXGroup; + children = ( + A67D5A9B28C1F51500624B25 /* ChoiceView.swift */, + A67D5A9D28C1F53100624B25 /* DocumentList.swift */, + A67D5A9F28C1F54900624B25 /* NewsList.swift */, + ); + path = View; + sourceTree = ""; + }; + A67D5AA228C58D5100624B25 /* Network */ = { + isa = PBXGroup; + children = ( + A67D5AA328C58D6F00624B25 /* NetworkManager.swift */, + ); + path = Network; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A67D5A6F28C0537D00624B25 /* NaverOpenAPITest */ = { + isa = PBXNativeTarget; + buildConfigurationList = A67D5A7E28C0537F00624B25 /* Build configuration list for PBXNativeTarget "NaverOpenAPITest" */; + buildPhases = ( + A67D5A6C28C0537D00624B25 /* Sources */, + A67D5A6D28C0537D00624B25 /* Frameworks */, + A67D5A6E28C0537D00624B25 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NaverOpenAPITest; + productName = NaverOpenAPITest; + productReference = A67D5A7028C0537D00624B25 /* NaverOpenAPITest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A67D5A6828C0537D00624B25 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1340; + LastUpgradeCheck = 1340; + TargetAttributes = { + A67D5A6F28C0537D00624B25 = { + CreatedOnToolsVersion = 13.4.1; + }; + }; + }; + buildConfigurationList = A67D5A6B28C0537D00624B25 /* Build configuration list for PBXProject "NaverOpenAPITest" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A67D5A6728C0537D00624B25; + productRefGroup = A67D5A7128C0537D00624B25 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A67D5A6F28C0537D00624B25 /* NaverOpenAPITest */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A67D5A6E28C0537D00624B25 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A67D5A7B28C0537F00624B25 /* Preview Assets.xcassets in Resources */, + A67D5A7828C0537F00624B25 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A67D5A6C28C0537D00624B25 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A67D5A8C28C1B74000624B25 /* Finder.swift in Sources */, + A67D5A9C28C1F51500624B25 /* ChoiceView.swift in Sources */, + A67D5A8E28C1CD2E00624B25 /* NewsDesk.swift in Sources */, + A67D5A7428C0537D00624B25 /* NaverOpenAPITestApp.swift in Sources */, + A67D5AA428C58D6F00624B25 /* NetworkManager.swift in Sources */, + A67D5AA028C1F54900624B25 /* NewsList.swift in Sources */, + A67D5A9328C1DBCD00624B25 /* String+Extension.swift in Sources */, + A67D5A9E28C1F53100624B25 /* DocumentList.swift in Sources */, + A67D5A9128C1CD6D00624B25 /* Encyclopedia.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A67D5A7C28C0537F00624B25 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A67D5A7D28C0537F00624B25 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A67D5A7F28C0537F00624B25 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"NaverOpenAPITest/Preview Content\""; + DEVELOPMENT_TEAM = 3N2D6D7SCN; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.ohs.NaverOpenAPITest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A67D5A8028C0537F00624B25 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"NaverOpenAPITest/Preview Content\""; + DEVELOPMENT_TEAM = 3N2D6D7SCN; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.ohs.NaverOpenAPITest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A67D5A6B28C0537D00624B25 /* Build configuration list for PBXProject "NaverOpenAPITest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A67D5A7C28C0537F00624B25 /* Debug */, + A67D5A7D28C0537F00624B25 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A67D5A7E28C0537F00624B25 /* Build configuration list for PBXNativeTarget "NaverOpenAPITest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A67D5A7F28C0537F00624B25 /* Debug */, + A67D5A8028C0537F00624B25 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A67D5A6828C0537D00624B25 /* Project object */; +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/AccentColor.colorset/Contents.json b/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/AppIcon.appiconset/Contents.json b/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/Contents.json b/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Extension/String+Extension.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/Extension/String+Extension.swift new file mode 100644 index 0000000..f5d35cb --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Extension/String+Extension.swift @@ -0,0 +1,20 @@ +// +// AttributedString+Extension.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + +import Foundation + +extension AttributedString { + /// HTML형식의 문자열을 기본 문자열로 변환합니다. + /// - Parameter htmlString: HTML 형식의 문자열. + init?(htmlString: String) { + let option: [NSAttributedString.DocumentReadingOptionKey: NSAttributedString.DocumentType] = [.documentType: .html] + guard let htmlData = htmlString.data(using: .utf16), + let nsStr = try? NSAttributedString(data: htmlData, options: option, documentAttributes: nil) + else { return nil } + self.init(nsStr.string) + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Model/Encyclopedia.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/Model/Encyclopedia.swift new file mode 100644 index 0000000..f9e8c2f --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Model/Encyclopedia.swift @@ -0,0 +1,33 @@ +// +// Department.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + +import Foundation + +struct Encyclopedia { + var searchKeyword = "" + var documents: [Document] = [] + var path = "/v1/search/encyc.json" +} + +extension Encyclopedia { + + struct Document: Codable, Identifiable { + let attributedTitle: AttributedString? + let link: String + let description: String + let tumbnail: String + let id: Int + + init(_ document: DocumentResponse.Document, id: Int) { + attributedTitle = AttributedString(htmlString: document.title) + link = document.link + description = document.description + tumbnail = document.thumbnail + self.id = id + } + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Model/NewsDesk.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/Model/NewsDesk.swift new file mode 100644 index 0000000..a755675 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Model/NewsDesk.swift @@ -0,0 +1,35 @@ +// +// NewsDesk.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + +import Foundation + +struct NewsDesk { + var searchKeyword = "" + var news: [News] = [] + var path = "/v1/search/news.json" +} + +extension NewsDesk{ + + struct News: Codable, Identifiable { + let attributedTitle: AttributedString? + let originalLink: String + let link: String + let description: String + let pubDate: String + let id: Int + + init(_ news: NewsResponse.News, id: Int) { + attributedTitle = AttributedString(htmlString: news.title) + pubDate = news.pubDate + originalLink = news.originallink + link = news.link + description = news.description + self.id = id + } + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/NaverOpenAPITestApp.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/NaverOpenAPITestApp.swift new file mode 100644 index 0000000..decc563 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/NaverOpenAPITestApp.swift @@ -0,0 +1,19 @@ +// +// NaverOpenAPITestApp.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/01. +// + +import SwiftUI + +@main +struct NaverOpenAPITestApp: App { + + var body: some Scene { + WindowGroup { + ChoiceView() + } + } +} + diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Network/NetworkManager.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/Network/NetworkManager.swift new file mode 100644 index 0000000..7186a4e --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Network/NetworkManager.swift @@ -0,0 +1,86 @@ +// +// NetworkManager.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/05. +// + +import Foundation + +fileprivate enum NaverOpenAPI { + static let clientID = "QDph9OaM58FywxjbaSDe" + static let clientSecret = "7BEzAGG02o" + static let scheme = "https" + static let host = "openapi.naver.com" +} + +class NetworkManager { + + private func fillUrlComponents(path: String, query: String) -> URLComponents { + var urlComponents = URLComponents() + urlComponents.scheme = NaverOpenAPI.scheme + urlComponents.host = NaverOpenAPI.host + urlComponents.path = path + urlComponents.queryItems = [URLQueryItem(name: "query", value: query)] + return urlComponents + } + + private func fillUrlReuqest(url: URL, httpMethod: String) -> URLRequest { + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = httpMethod + urlRequest.addValue(NaverOpenAPI.clientID, forHTTPHeaderField: "X-Naver-Client-Id") + urlRequest.addValue(NaverOpenAPI.clientSecret, forHTTPHeaderField: "X-Naver-Client-Secret") + return urlRequest + } + + func fetchNewsList(path: String, query: String, fetchDataInBackground: @escaping (_ parsedData: NewsResponse) -> Void) { + let urlComponents = fillUrlComponents(path: path, query: query) + guard let url = urlComponents.url else { + return + } + let urlRequest = fillUrlReuqest(url: url, httpMethod: HttpMethod.get.rawValue) + let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in + if error != nil { + return + } + guard let response = response as? HTTPURLResponse, + (200...299).contains(response.statusCode) else { + return + } + guard let data = data, let parsedData = try? JSONDecoder().decode(NewsResponse.self, from: data) else { + return + } + fetchDataInBackground(parsedData) + } + task.resume() + } + + func fetchDocumentList(path: String, query: String, fetchDataInBackground: @escaping (_ parsedData: DocumentResponse) -> Void) { + let urlComponents = fillUrlComponents(path: path, query: query) + guard let url = urlComponents.url else { + return + } + let urlRequest = fillUrlReuqest(url: url, httpMethod: HttpMethod.get.rawValue) + let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in + if error != nil { + return + } + guard let response = response as? HTTPURLResponse, + (200...299).contains(response.statusCode) else { + return + } + guard let data = data, let parsedData = try? JSONDecoder().decode(DocumentResponse.self, from: data) else { + return + } + fetchDataInBackground(parsedData) + } + task.resume() + } +} + +enum HttpMethod: String { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/Preview Content/Preview Assets.xcassets/Contents.json b/week5/NaverOpenAPITest/NaverOpenAPITest/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/View/ChoiceView.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/View/ChoiceView.swift new file mode 100644 index 0000000..10870c9 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/View/ChoiceView.swift @@ -0,0 +1,26 @@ +// +// ChoiceView.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + +import SwiftUI + +struct ChoiceView: View { + @StateObject var finder = Finder() + var body: some View { + NavigationView { + List { + NavigationLink("Search News", destination: NewsList(newsFinder: finder)) + NavigationLink("Search Document", destination: DocumentList(documentFinder: finder)) + } + } + } +} + +struct ChoiceView_Previews: PreviewProvider { + static var previews: some View { + ChoiceView() + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/View/DocumentList.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/View/DocumentList.swift new file mode 100644 index 0000000..474d835 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/View/DocumentList.swift @@ -0,0 +1,49 @@ +// +// DocumentList.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + +import SwiftUI + +struct DocumentList: View { + @ObservedObject var documentFinder: Finder + + var body: some View { + ZStack { + List { + HStack { + TextField(text: $documentFinder.encyclopedia.searchKeyword, label: { + Text("검색어를 입력하세요.") }) + Button(action: {documentFinder.fetchDocument() }) { + Text("검색") + } + .buttonStyle(.bordered) + } + Section { + ForEach(documentFinder.encyclopedia.documents) { movie in + VStack { + Spacer() + Group { + if let attributedTitle = movie.attributedTitle { + Text(attributedTitle) + } else { + Text("알 수 없음") + } + } + .font(Font.system(size: 15)) + .minimumScaleFactor(0.5) + Spacer() + } + } + } + } + if documentFinder.fetchingStatus == .fetching { + ProgressView() + .scaleEffect(1.5) + } + } + .foregroundColor(.black) + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/View/NewsList.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/View/NewsList.swift new file mode 100644 index 0000000..b3ec923 --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/View/NewsList.swift @@ -0,0 +1,50 @@ +// +// NewsList.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + + +import SwiftUI + +struct NewsList: View { + @ObservedObject var newsFinder: Finder + + var body: some View { + ZStack { + List { + HStack { + TextField(text: $newsFinder.newsDesk.searchKeyword, label: { + Text("검색어를 입력하세요.") }) + Button(action: {newsFinder.fetchNews() }) { + Text("검색") + } + .buttonStyle(.bordered) + } + Section { + ForEach(newsFinder.newsDesk.news) { movie in + VStack { + Spacer() + Group { + if let attributedTitle = movie.attributedTitle { + Text(attributedTitle) + } else { + Text("알 수 없음") + } + } + .font(Font.system(size: 15)) + .minimumScaleFactor(0.5) + Spacer() + } + } + } + } + if newsFinder.fetchingStatus == .fetching { + ProgressView() + .scaleEffect(1.5) + } + } + .foregroundColor(.black) + } +} diff --git a/week5/NaverOpenAPITest/NaverOpenAPITest/ViewModel/Finder.swift b/week5/NaverOpenAPITest/NaverOpenAPITest/ViewModel/Finder.swift new file mode 100644 index 0000000..1d0f95d --- /dev/null +++ b/week5/NaverOpenAPITest/NaverOpenAPITest/ViewModel/Finder.swift @@ -0,0 +1,75 @@ +// +// Network.swift +// NaverOpenAPITest +// +// Created by ohhyeongseok on 2022/09/02. +// + +import Foundation + +class Finder: ObservableObject { + @Published var newsDesk = NewsDesk() + @Published var encyclopedia = Encyclopedia() + @Published var fetchingStatus = FetchStatus.idle + let networkManager = NetworkManager() + + func fetchNews() { + fetchingStatus = .fetching + networkManager.fetchNewsList(path: newsDesk.path, query: newsDesk.searchKeyword) { parsedData in + DispatchQueue.main.async { [weak self] in + self?.newsDesk.news = parsedData.items.indices.map { + NewsDesk.News(parsedData.items[$0], id: parsedData.start + $0) + } + self?.fetchingStatus = .idle + } + } + } + + func fetchDocument() { + fetchingStatus = .fetching + networkManager.fetchDocumentList(path: encyclopedia.path, query: encyclopedia.searchKeyword) { parsedData in + DispatchQueue.main.async { [weak self] in + self?.encyclopedia.documents = parsedData.items.indices.map { + Encyclopedia.Document(parsedData.items[$0], id: parsedData.start + $0) + } + self?.fetchingStatus = .idle + } + } + } + + enum FetchStatus { + case idle + case fetching + } +} + +struct NewsResponse: Codable { + let lastBuildDate: String + let total: Int + let start: Int + let display: Int + let items: [News] + + struct News: Codable { + let title: String + let originallink: String + let link: String + let description: String + let pubDate: String + } +} + +struct DocumentResponse: Codable { + let lastBuildDate: String + let total: Int + let start: Int + let display: Int + let items: [Document] + + struct Document: Codable { + let title: String + let link: String + let description: String + let thumbnail: String + } +}