Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
34 changes: 29 additions & 5 deletions GameScope/GameScope.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
F1E20C7C2A4D6E1C00772B80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F1E20C7B2A4D6E1C00772B80 /* Assets.xcassets */; };
F1E20C7F2A4D6E1C00772B80 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F1E20C7D2A4D6E1C00772B80 /* LaunchScreen.storyboard */; };
F1E20C8D2A4EACF900772B80 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = F1E20C8C2A4EACF900772B80 /* SnapKit */; };
F60B0D062A6E03CA004CE3D5 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60B0D052A6E03CA004CE3D5 /* DetailViewController.swift */; };
F60B0D082A6E42C5004CE3D5 /* GameDetailImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60B0D072A6E42C5004CE3D5 /* GameDetailImageCell.swift */; };
F6B45FA82A4DA96000F1D0DB /* popular-pvp-games.json in Resources */ = {isa = PBXBuildFile; fileRef = F6B45FA72A4DA96000F1D0DB /* popular-pvp-games.json */; };
F6B45FAA2A4DA9C200F1D0DB /* detail-game-pubg.json in Resources */ = {isa = PBXBuildFile; fileRef = F6B45FA92A4DA9C200F1D0DB /* detail-game-pubg.json */; };
F6B45FAC2A4E71DA00F1D0DB /* GameListDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FAB2A4E71DA00F1D0DB /* GameListDTO.swift */; };
Expand All @@ -43,8 +45,10 @@
F6B45FD32A4EC36C00F1D0DB /* detail-game-pubg.json in Resources */ = {isa = PBXBuildFile; fileRef = F6B45FA92A4DA9C200F1D0DB /* detail-game-pubg.json */; };
F6B45FD52A4EC5D900F1D0DB /* GameDetailDTOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FD42A4EC5D900F1D0DB /* GameDetailDTOTests.swift */; };
F6B45FD62A4EC6A000F1D0DB /* wrong-game-pubg.json in Resources */ = {isa = PBXBuildFile; fileRef = F6B45FD22A4EBF4A00F1D0DB /* wrong-game-pubg.json */; };
F6B45FD82A500F5200F1D0DB /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FD72A500F5200F1D0DB /* DetailViewController.swift */; };
F6B45FDF2A57A10D00F1D0DB /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FDE2A57A10D00F1D0DB /* HeaderView.swift */; };
F6B45FEA2A5F962200F1D0DB /* GameDetailInformationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FE92A5F962200F1D0DB /* GameDetailInformationCell.swift */; };
F6B45FEC2A5F97DF00F1D0DB /* InformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FEB2A5F97DF00F1D0DB /* InformationView.swift */; };
F6B45FEE2A63711F00F1D0DB /* GameDetailDescriptionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B45FED2A63711F00F1D0DB /* GameDetailDescriptionCell.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -75,6 +79,8 @@
F1E20C7B2A4D6E1C00772B80 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
F1E20C7E2A4D6E1C00772B80 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
F1E20C802A4D6E1C00772B80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F60B0D052A6E03CA004CE3D5 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
F60B0D072A6E42C5004CE3D5 /* GameDetailImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailImageCell.swift; sourceTree = "<group>"; };
F6B45FA72A4DA96000F1D0DB /* popular-pvp-games.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "popular-pvp-games.json"; sourceTree = "<group>"; };
F6B45FA92A4DA9C200F1D0DB /* detail-game-pubg.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "detail-game-pubg.json"; sourceTree = "<group>"; };
F6B45FAB2A4E71DA00F1D0DB /* GameListDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameListDTO.swift; sourceTree = "<group>"; };
Expand All @@ -88,8 +94,10 @@
F6B45FD02A4EBEEA00F1D0DB /* wrong-pvp-games.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wrong-pvp-games.json"; sourceTree = "<group>"; };
F6B45FD22A4EBF4A00F1D0DB /* wrong-game-pubg.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wrong-game-pubg.json"; sourceTree = "<group>"; };
F6B45FD42A4EC5D900F1D0DB /* GameDetailDTOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailDTOTests.swift; sourceTree = "<group>"; };
F6B45FD72A500F5200F1D0DB /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
F6B45FDE2A57A10D00F1D0DB /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
F6B45FE92A5F962200F1D0DB /* GameDetailInformationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailInformationCell.swift; sourceTree = "<group>"; };
F6B45FEB2A5F97DF00F1D0DB /* InformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationView.swift; sourceTree = "<group>"; };
F6B45FED2A63711F00F1D0DB /* GameDetailDescriptionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailDescriptionCell.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -115,6 +123,7 @@
isa = PBXGroup;
children = (
F14B01752A513FEF00D43B1D /* GameListCollectionViewController.swift */,
F60B0D052A6E03CA004CE3D5 /* DetailViewController.swift */,
);
path = Controllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -149,7 +158,12 @@
F15550702A4EDCEC00832E92 /* View */ = {
isa = PBXGroup;
children = (
F6B45FDE2A57A10D00F1D0DB /* HeaderView.swift */,
F6B45FE92A5F962200F1D0DB /* GameDetailInformationCell.swift */,
F6B45FEB2A5F97DF00F1D0DB /* InformationView.swift */,
F6B45FED2A63711F00F1D0DB /* GameDetailDescriptionCell.swift */,
F15550712A4EDD0400832E92 /* GameListCollectionViewCell.swift */,
F60B0D072A6E42C5004CE3D5 /* GameDetailImageCell.swift */,
);
path = View;
sourceTree = "<group>";
Expand All @@ -160,6 +174,7 @@
F1E20C712A4D6E1B00772B80 /* GameScope */,
F6B45FC12A4EB9CC00F1D0DB /* GameScopeTests */,
F1E20C702A4D6E1B00772B80 /* Products */,
F60B0D042A6E036E004CE3D5 /* Recovered References */,
);
sourceTree = "<group>";
};
Expand All @@ -177,8 +192,6 @@
children = (
F1E20C722A4D6E1B00772B80 /* AppDelegate.swift */,
F1E20C742A4D6E1B00772B80 /* SceneDelegate.swift */,
F6B45FD72A500F5200F1D0DB /* DetailViewController.swift */,
F6B45FDE2A57A10D00F1D0DB /* HeaderView.swift */,
F1E20C782A4D6E1B00772B80 /* Main.storyboard */,
F1E20C7B2A4D6E1C00772B80 /* Assets.xcassets */,
F1E20C7D2A4D6E1C00772B80 /* LaunchScreen.storyboard */,
Expand All @@ -197,6 +210,13 @@
path = GameScope;
sourceTree = "<group>";
};
F60B0D042A6E036E004CE3D5 /* Recovered References */ = {
isa = PBXGroup;
children = (
);
name = "Recovered References";
sourceTree = "<group>";
};
F6B45FB42A4E7A1800F1D0DB /* DTO */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -350,17 +370,21 @@
F1E20C732A4D6E1B00772B80 /* AppDelegate.swift in Sources */,
F14B017B2A5195DD00D43B1D /* GameManager.swift in Sources */,
F14B01762A513FEF00D43B1D /* GameListCollectionViewController.swift in Sources */,
F60B0D062A6E03CA004CE3D5 /* DetailViewController.swift in Sources */,
F6B45FAE2A4E752900F1D0DB /* GameDetailDTO.swift in Sources */,
F6B45FB02A4E768B00F1D0DB /* MinimumSystemRequirementsDTO.swift in Sources */,
F14B01952A56655000D43B1D /* URLResponse+.swift in Sources */,
F1E20C752A4D6E1B00772B80 /* SceneDelegate.swift in Sources */,
F6B45FEC2A5F97DF00F1D0DB /* InformationView.swift in Sources */,
F1813CD92A623DED00D77CD8 /* DateHandler.swift in Sources */,
F6B45FB22A4E775100F1D0DB /* ScreenshotDTO.swift in Sources */,
F6B45FDF2A57A10D00F1D0DB /* HeaderView.swift in Sources */,
F6B45FD82A500F5200F1D0DB /* DetailViewController.swift in Sources */,
F6B45FEE2A63711F00F1D0DB /* GameDetailDescriptionCell.swift in Sources */,
F60B0D082A6E42C5004CE3D5 /* GameDetailImageCell.swift in Sources */,
F6B45FB92A4EA9E700F1D0DB /* GameList.swift in Sources */,
F14B017D2A52AF2B00D43B1D /* JSONDeserializer.swift in Sources */,
F6B45FAC2A4E71DA00F1D0DB /* GameListDTO.swift in Sources */,
F6B45FEA2A5F962200F1D0DB /* GameDetailInformationCell.swift in Sources */,
F14B018E2A565B7C00D43B1D /* NetworkDispatcher.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
190 changes: 190 additions & 0 deletions GameScope/GameScope/Controller/DetailViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//
// DetailViewController.swift
// GameScope
//
// Created by 박재우 on 2023/07/01.
//

import UIKit
import SnapKit

class DetailViewController: UIViewController, UICollectionViewDelegate {

static let headerElementKind = "header-element-kind"

enum Section: String, CaseIterable {
case thumbnail
case about = "About"
case information = "Information"
case screenshots = "ScreenShots"
}

private lazy var detailCollectionView = {
let collectionView = UICollectionView(
frame: view.bounds,
collectionViewLayout: generateLayout())

collectionView.backgroundColor = .systemBackground
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(
HeaderView.self,
forSupplementaryViewOfKind: DetailViewController.headerElementKind,
withReuseIdentifier: HeaderView.reuseIdentifier)
collectionView.register(
GameDetailInformationCell.self,
forCellWithReuseIdentifier: GameDetailInformationCell.reuseIdentifier)
collectionView.register(
GameDetailDescriptionCell.self,
forCellWithReuseIdentifier: GameDetailDescriptionCell.reuseIdentifier)
return collectionView
}()
private let boundarySupplementaryHeader = {
let headerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: DetailViewController.headerElementKind,
alignment: .top)
return sectionHeader
}()

private var detail: GameDetail?

convenience init(gameDetail: GameDetail) {
self.init()
detail = gameDetail
}

override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = detail?.title

view.addSubview(detailCollectionView)
}

}

extension DetailViewController {

private func generateLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let sectionLayoutKind = Section.allCases[sectionIndex]
switch sectionLayoutKind {
case .thumbnail:
return nil
case .about:
return self.generateAboutLayout()
case .information:
return self.generateInformationLayout()
case .screenshots:
return nil
}
}
return layout
}

private func generateAboutLayout() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.9),
heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
subitem: item,
count: 1)

let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [boundarySupplementaryHeader]
section.orthogonalScrollingBehavior = .groupPagingCentered

return section
}

private func generateInformationLayout() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.9),
heightDimension: .fractionalWidth(0.4))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize,
subitem: item,
count: 1)

let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [boundarySupplementaryHeader]
section.orthogonalScrollingBehavior = .groupPagingCentered

return section
}
}

extension DetailViewController: UICollectionViewDataSource {

func numberOfSections(in collectionView: UICollectionView) -> Int {
Section.allCases.count
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch Section.allCases[section] {
case .about, .information:
return 1
default:
return 0
}
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch Section.allCases[indexPath.section] {
case .about:
guard let detail,
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: GameDetailDescriptionCell.reuseIdentifier,
for: indexPath) as? GameDetailDescriptionCell else {
return UICollectionViewCell()
}
cell.configure(description: detail.description)
cell.delegate = self
return cell
case .information:
guard let detail = detail,
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: GameDetailInformationCell.reuseIdentifier,
for: indexPath) as? GameDetailInformationCell else {
return UICollectionViewCell()
}
cell.configure(information: detail)
return cell
default:
return UICollectionViewCell()
}
}

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == DetailViewController.headerElementKind,
let HeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderView.reuseIdentifier, for: indexPath) as? HeaderView {
HeaderView.label.text = Section.allCases[indexPath.section].rawValue
return HeaderView
}

return UICollectionReusableView()
}

}

extension DetailViewController: GameDetailDescriptionCellDelegate {
func gameDetailDescriptionCell(
_ gameDetailDescriptionCell: GameDetailDescriptionCell,
didButtonTapped sender: UIButton
) {
gameDetailDescriptionCell.expandDetailDescription()
self.detailCollectionView.reloadData()
}
}
Loading