์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- reactorkit
- rxcocoa
- ํจ์คํธ์บ ํผ์ค
- SnapKit
- ๋ฐฑ์ค
- Kuring
- Swfit
- ํ๋ก๊ทธ๋๋จธ์ค
- BFS
- designpattern
- combine
- CollectionView
- Lv2
- tableView
- XCTest
- raywenderlich
- Flutter
- swift
- ios
- TCA
- RxSwift
- visionOS
- arkit
- Xcode
- node.js
- BOJ
- realm
- SwiftUI
- MVVM
- UIKit
- Today
- Total
lgvv98
part4 (ch1). MyAssets ์ฝ๋๋ฆฌ๋ทฐ(feat. SwiftUI) ๋ณธ๋ฌธ
part4 (ch1). MyAssets ์ฝ๋๋ฆฌ๋ทฐ(feat. SwiftUI)
๐ฅ ์บ๋ฟ๋งจ 2022. 2. 23. 15:31MyAssets ์ฝ๋๋ฆฌ๋ทฐ(feat. SwiftUI)
โ ๊ณต๋ถ๋ฅผ ํ์ผ๋ ์ฝ๋ ๋ฆฌ๋ทฐ๋ ์งํํด๋ณด์.
SwiftUI๋ฅผ ๋ณด๋ฉด์ ๋ ์๊ฐ์ธ๋ฐ, Obj-c๊ฐ swift๋ก ๋์ด์๊ณ , ์ธ์ ๊ฐ๋ SwiftUI๋ก ์ ๋ถ ๋ค ๋์ด๊ฐ์ง ์์๊น ์ถ๋ค.
์ฐ์ ์ฒซ๋ฒ์งธ๋ก, ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ด UI๊ตฌ์ฑ์ด ์์ฒญ๋๊ฒ ๊ฐ๋จํ๊ณ , ๊ฐ๋ฐ ์๋๊ฐ ์์ฒญ๋๊ฒ ๋นจ๋ผ์ง ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
SwiftUI๋ฅผ Tutorial๋ก ๋ ๊ณต๋ถํ๊ณ ๋์ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋ ์์ธํ ํด๋ณด์~!
โ ํด๋์ ๊ตฌ์กฐ
โ ContentView.swift
//
// ContentView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/22.
//
import SwiftUI
struct ContentView: View {
@State private var selection: Tab = .asset
enum Tab {
case asset
case recommend
case alert
case setting
}
var body: some View {
TabView(selection: $selection) { // ํ๋จ ํญ๋ฐ ์์ดํ
AssetView()
.tabItem {
Image(systemName: "dollarsign.circle.fill")
Text("์์ฐ")
}
.tag(Tab.asset) // ํ๊ทธ๋ฅผ ๋ฌ์์ค๋ค.
Color.blue
.edgesIgnoringSafeArea(.all)
.tabItem {
Image(systemName: "hand.thumbsup.fill")
Text("์ถ์ฒ")
}
.tag(Tab.recommend)
Color.red
.edgesIgnoringSafeArea(.all)
.tabItem {
Image(systemName: "bell.fill")
Text("์๋ฆผ")
}
.tag(Tab.alert)
Color.yellow
.edgesIgnoringSafeArea(.all)
.tabItem {
Image(systemName: "gearshape.fill")
Text("์ค์ ")
}
.tag(Tab.setting)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
โ Asset ํด๋
๐ AssetView.swift
//
// AssetView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetView: View {
var body: some View {
NavigationView { // ๋ค๋น๊ฒ์ด์
view
ScrollView { // ์ค์ ์์๋ ์คํฌ๋กค์ ์ง์ ๋ฌ์์ค์ผ ํ๋ค.
VStack(spacing: 30) { // ๊ฐ ์ปดํฌ๋ํธ๊ฐ spacing
Spacer()
AssetMenuGridView()
AssetBannerView()
.aspectRatio(5/2, contentMode: .fit)
AssetSummaryView()
.environmentObject(AssetSummaryData())
}
}
.background(Color.gray.opacity(0.2))
.navigaionBarWithButtonStyle("๋ด ์์ฐ")
}
}
}
struct AssetView_Previews: PreviewProvider {
static var previews: some View {
AssetView()
}
}
๐ AssetMenu.swift
//
// AssetMenu.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/22.
//
import Foundation
class Asset: Identifiable, ObservableObject, Decodable {
let id: Int
let type: AssetMenu
let data: [AssetData]
init(id: Int, type: AssetMenu, data: [AssetData]) {
self.id = id
self.type = type
self.data = data
}
}
class AssetData: Identifiable, ObservableObject, Decodable {
let id: Int
let title: String
let amount: String
let creditCardAmounts: [CreditCardAmounts]?
init(id: Int, title: String, amount: String, creditCardAmounts: [CreditCardAmounts]? = nil) {
self.id = id
self.title = title
self.amount = amount
self.creditCardAmounts = creditCardAmounts
}
}
enum CreditCardAmounts: Identifiable, Decodable {
case previousMonth(amount: String)
case currentMonth(amount: String)
case nextMonth(amount: String)
var id: Int {
switch self {
case .previousMonth:
return 0
case .currentMonth:
return 1
case .nextMonth:
return 2
}
}
var amount: String {
switch self {
case .previousMonth(let amount),
.currentMonth(let amount),
.nextMonth(let amount):
return amount
}
}
enum CodingKeys: String, CodingKey {
case previousMonth
case currentMonth
case nextMonth
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? values.decode(String.self, forKey: .previousMonth) {
self = .previousMonth(amount: value)
return
}
if let value = try? values.decode(String.self, forKey: .currentMonth) {
self = .previousMonth(amount: value)
return
}
if let value = try? values.decode(String.self, forKey: .nextMonth) {
self = .previousMonth(amount: value)
return
}
throw fatalError("ERROR: CreditCardAmount JSON Decoding")
}
}
enum AssetMenu: String, Identifiable, Decodable {
case creditScore
case bankAccount
case investment
case loan
case insurance
case creditCard
case cash
case realEstate
var id: String {
return self.rawValue
}
var systemImageName: String {
switch self {
case .creditScore:
return "number.circle"
case .bankAccount:
return "banknote"
case .investment:
return "bitcoinsign.circle"
case .loan:
return "hand.wave"
case .insurance:
return "lock.shield"
case .creditCard:
return "creditcard"
case .cash:
return "dollarsign.circle"
case .realEstate:
return "house.fill"
}
}
var title: String {
switch self {
case .creditScore:
return "์ ์ฉ์ ์"
case .bankAccount:
return "๊ณ์ข"
case .investment:
return "ํฌ์"
case .loan:
return "๋์ถ"
case .insurance:
return "๋ณดํ"
case .creditCard:
return "์นด๋"
case .cash:
return "ํ๊ธ์์์ฆ"
case .realEstate:
return "๋ถ๋์ฐ"
}
}
}
โ Subcomponents ํด๋
โ NavigationBar ํด๋
๐ NavigationVarWithButton.swift
//
// NavigationVarWithButton.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/22.
//
import SwiftUI
struct NavigationBarWithButton: ViewModifier {
var title: String = ""
func body(content: Content) -> some View {
return content
.navigationBarItems(
leading: Text(title)
.font(.system(size: 24, weight: .bold))
.padding(),
trailing: Button(
action: {
print("์์ฐ์ถ๊ฐ๋ฒํผ tapped")
},
label: {
Image(systemName: "plus")
Text("์์ฐ์ถ๊ฐ")
.font(.system(size: 12))
}
)
.accentColor(.black)
.padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black)
)
)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor =
UIColor(white: 1, alpha: 0.6)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
}
}
extension View {
func navigaionBarWithButtonStyle(_ title: String) -> some View {
return self.modifier(NavigationBarWithButton(title: title))
}
}
struct NavigationBarWithButton_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
Color.gray.edgesIgnoringSafeArea(.all)
.navigaionBarWithButtonStyle("๋ด ์์ฐ")
}
}
}
โ MenuGridView.swift
๐ AssetMenuGridView.swift
//
// AssetMenuGridView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetMenuGridView: View {
let menuList: [[AssetMenu]] = [
[.creditScore, .bankAccount, .investment, .loan],
[.insurance, .creditCard, .cash, .realEstate]
]
var body: some View {
VStack(spacing: 20) {
ForEach(menuList, id: \.self) { row in
HStack(spacing: 10) {
ForEach(row) { menu in
Button("") {
print("\(menu.title)๋ฒํผ tapped")
}
.buttonStyle(AssetMenuButtonStyle(menu: menu))
}
}
}
}
}
}
struct AssetMenuGridView_Previews: PreviewProvider {
static var previews: some View {
AssetMenuGridView()
}
}
๐ AssetMenuButtonStyle.swift
//
// AssetMenuButtonStyle.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/22.
//
import SwiftUI
struct AssetMenuButtonStyle: ButtonStyle {
let menu: AssetMenu
func makeBody(configuration: Configuration) -> some View {
return VStack {
Image(systemName: menu.systemImageName)
.resizable()
.frame(width: 30, height: 30)
.padding([.leading, .trailing], 10)
Text(menu.title)
.font(.system(size: 12, weight: .bold))
}
.padding()
.foregroundColor(.blue)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
struct AssetMenuButtonStyle_Previews: PreviewProvider {
static var previews: some View {
HStack(spacing: 24) {
Button("") {
print("")
}
.buttonStyle(AssetMenuButtonStyle(menu: .creditScore))
Button("") {
print("")
}
.buttonStyle(AssetMenuButtonStyle(menu: .bankAccount))
Button("") {
print("")
}
.buttonStyle(AssetMenuButtonStyle(menu: .creditCard))
Button("") {
print("")
}
.buttonStyle(AssetMenuButtonStyle(menu: .cash))
}
.background(Color.gray.opacity(0.2))
}
}
๐ AssetBannerView.swift
//
// AssetBannerView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetBannerView: View {
let bannerList: [AssetBanner] = [
AssetBanner(title: "๊ณต์ง์ฌํญ", description: "์ถ๊ฐ๋ ๊ณต์ง์ฌํญ์ ํ์ธํ์ธ์", backgroundColor: .red),
AssetBanner(title: "์ฃผ๋ง ์ด๋ฒคํธ", description: "์ฃผ๋ง ์ด๋ฒคํธ ๋์น์ง ๋ง์ธ์", backgroundColor: .yellow),
AssetBanner(title: "๊น์ง ์ด๋ฒคํธ", description: "์์ฒญ๋ ์ด๋ฒคํธ์ ๋๋ผ์ง ๋ง์ธ์", backgroundColor: .blue),
AssetBanner(title: "๊ฐ์ ํ๋ก๋ชจ์
", description: "๊ฐ์...๐", backgroundColor: .brown)
]
@State private var currentPage = 0
var body: some View {
let pages = bannerList.map { BannerCard(banner: $0) }
ZStack(alignment: .bottomTrailing) {
PageViewController(pages: pages, currentPage: $currentPage)
PageControl(numberOfPages: bannerList.count, currentPage: $currentPage)
.frame(width: CGFloat(pages.count * 18))
.padding(.trailing)
}
}
}
struct AssetBannerView_Previews: PreviewProvider {
static var previews: some View {
AssetBannerView()
.aspectRatio(5/2, contentMode: .fit)
}
}
๐ AssetBanner.swift
//
// AssetBanner.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import UIKit
struct AssetBanner {
let title: String
let description: String
let backgroundColor: UIColor
}
๐ BannerCard.swift
//
// BannerCard.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct BannerCard: View {
var banner: AssetBanner
var body: some View {
Color(banner.backgroundColor)
.overlay(
VStack {
Text(banner.title)
.font(.title)
Text(banner.description)
.font(.subheadline)
}
)
}
}
struct BannerCard_Previews: PreviewProvider {
static var previews: some View {
BannerCard(banner: AssetBanner(title: "๊ณต์ง์ฌํญ", description: "์ถ๊ฐ๋ ๊ณต์ง์ฌํญ์ ํ์ธํ์ธ์", backgroundColor: .red))
.aspectRatio(5/2, contentMode: .fit)
}
}
๐ AssetSummaryView.swift
//
// AssetSummaryView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetSummaryView: View {
@EnvironmentObject var assetData: AssetSummaryData
var assets: [Asset] {
return assetData.assets
}
var body: some View {
VStack(spacing: 20) {
ForEach(assets) { asset in
switch asset.type {
case .creditCard:
AssetCardSectionView(asset: asset)
.frame(idealHeight: 250)
default:
AssetSectionView(assetSection: asset)
}
}
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding()
}
}
}
struct AssetSummaryView_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
AssetSummaryView()
.environmentObject(AssetSummaryData())
.background(Color.gray.opacity(0.2))
}
}
}
๐ AssetSummaryData.swift
//
// AssetSummaryData.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
class AssetSummaryData: ObservableObject {
@Published var assets: [Asset] = load("assets.json")
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
๐ assets.json
[
{
"id": 0,
"type": "creditScore",
"data": [
{
"id": 0,
"title": "์ ์ฉ์ ์",
"amount": "999์ "
}
]
},
{
"id": 1,
"type": "bankAccount",
"data": [
{
"id": 0,
"title": "์ ํ์ํ",
"amount": "42,000์"
},
{
"id": 1,
"title": "๊ตญ๋ฏผ์ํ",
"amount": "9,263,000์"
},
{
"id": 2,
"title": "์นด์นด์ค๋ฑ
ํฌ",
"amount": "2,255,900์"
}
]
},
{
"id": 2,
"type": "investment",
"data": [
{
"id": 0,
"title": "์นด์นด์คํ์ด",
"amount": "5,003,370์"
},
{
"id": 1,
"title": "ํ๊ตญํฌ์",
"amount": "5,675,236์"
}
]
},
{
"id": 3,
"type": "loan",
"data": [
{
"id": 0,
"title": "์นด์นด์ค๋ฑ
ํฌ",
"amount": "-67,333,000์"
},
{
"id": 1,
"title": "ํ๋์ํ",
"amount": "-4,000,000,000์"
}
]
},
{
"id": 4,
"type": "insurance",
"data": [
{
"id": 0,
"title": "์ผ์ฑํ์ฌ",
"amount": "์ 20,000์"
},
{
"id": 1,
"title": "ํํ์ํด๋ณดํ",
"amount": "์ 77,400์"
},
{
"id": 2,
"title": "๋ฉ๋ฆฌ์ธ ํ์ฌ๋ณดํ",
"amount": "์ 10,000์"
},
{
"id": 3,
"title": "๋กฏ๋ฐ์ํด๋ณดํ",
"amount": "์ 84,900์"
}
]
},
{
"id": 5,
"type": "creditCard",
"data": [
{
"id": 0,
"title": "ํ๋์นด๋",
"amount": "0์",
"creditCardAmounts": [
{
"previousMonth": "10,000์"
},
{
"currentMonth": "45,000์"
},
{
"nextMonth": "100,400์"
}
]
},
{
"id": 1,
"title": "์ฐ๋ฆฌ์นด๋",
"amount": "9,000์",
"creditCardAmounts": [
{
"previousMonth": "40,000์"
},
{
"currentMonth": "95,000์"
},
{
"nextMonth": "2,150,400์"
}
]
}
]
},
{
"id": 6,
"type": "cash",
"data": [
{
"id": 0,
"title": "์ด๋ฒ ๋ฌ ์ฌ์ฉ๊ธ์ก",
"amount": "472,890์"
}
]
},
{
"id": 7,
"type": "realEstate",
"data": [
{
"id": 0,
"title": "ํ๊ฐํ๋์ํํธ",
"amount": "16์ต 9์ฒ๋ง์"
},
{
"id": 1,
"title": "์ฌ์๋์คํผ์คํ
",
"amount": "2์ต 9์ฒ๋ง์"
}
]
}
]
๐ AssetSectionViewHeaderView.swift
//
// AssetSectionViewHeaderView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetSectionHeaderView: View {
let title: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.system(size: 20, weight: .bold))
.foregroundColor(.accentColor)
Divider()
.frame(height: 2)
.background(Color.primary)
.foregroundColor(.accentColor)
}
}
}
struct AssetSectionHeaderView_Previews: PreviewProvider {
static var previews: some View {
AssetSectionHeaderView(title: "์ํ")
}
}
๐ SwiftUIView.swift
//
// SwiftUIView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetCardSectionView: View {
@State private var selectedTab = 1
@ObservedObject var asset: Asset
var assetData: [AssetData] {
return asset.data
}
var body: some View {
VStack {
AssetSectionHeaderView(title: asset.type.title)
TabMenuView(
tabs: ["์ง๋๋ฌ ๊ฒฐ์ ", "์ด๋ฒ๋ฌ ๊ฒฐ์ ", "๋ค์๋ฌ ๊ฒฐ์ "],
selectedTab: $selectedTab,
updated: .constant(.nextMonth(amount: "10000"))
)
TabView(
selection: $selectedTab,
content: {
ForEach(0...2, id: \.self) { i in
VStack {
ForEach(asset.data) { data in
HStack {
Text(data.title)
.font(.title3)
.foregroundColor(.secondary)
Spacer()
Text(data.creditCardAmounts![i].amount)
.font(.title2)
.foregroundColor(.primary)
}
Divider()
}
}
.tag(i)
}
}
)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
.padding()
}
}
struct AssetCardSectionView_Previews: PreviewProvider {
static var previews: some View {
let asset = AssetSummaryData().assets[5]
AssetCardSectionView(asset: asset)
}
}
๐ AssetMenuList.swift
//
// AssetMenuList.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct TabMenuView: View {
var tabs: [String]
@Binding var selectedTab: Int
@Binding var updated: CreditCardAmounts?
var body: some View {
HStack {
ForEach(0..<tabs.count, id: \.self) { row in
Button(
action: {
selectedTab = row
UserDefaults.standard.set(true, forKey: "creditcard_update_checked: \(row)")
},
label: {
VStack(spacing: 0) {
HStack {
if updated?.id == row {
let checked = UserDefaults.standard.bool(forKey: "creditcard_update_checked: \(row)")
Circle()
.fill(
!checked
? Color.red
: Color.clear
)
.frame(width: 6, height: 6)
.offset(x: 2, y: -8)
} else {
Circle()
.fill(Color.clear)
.frame(width: 6, height: 6)
.offset(x: 2, y: -8)
}
Text(tabs[row])
.font(.system(size: 20, weight: .bold))
.foregroundColor(
selectedTab == row
? .accentColor
: .gray
)
.offset(x: -4, y: 0)
}
.frame(height: 52)
Rectangle()
.fill(selectedTab == row
? Color.secondary
: Color.clear)
.frame(height: 3)
.offset(x: 4, y: 0)
}
})
.buttonStyle(PlainButtonStyle())
}
}
.frame(height: 55)
}
}
struct TabMenuView_Previews: PreviewProvider {
static var previews: some View {
TabMenuView(tabs: ["์ง๋๋ฌ ๊ฒฐ์ ", "์ด๋ฒ๋ฌ ๊ฒฐ์ ", "๋ค์๋ฌ ๊ฒฐ์ "], selectedTab: .constant(1), updated: .constant(.currentMonth(amount: "10,000์")))
}
}
๐ AssetSectionView.swift
//
// AssetSectionView.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
struct AssetSectionView: View {
@ObservedObject var assetSection: Asset
var body: some View {
VStack(spacing: 20) {
AssetSectionHeaderView(title: assetSection.type.title)
ForEach(assetSection.data) { asset in
HStack {
Text(asset.title)
.font(.title3)
.foregroundColor(.secondary)
Spacer()
Text(asset.amount)
.font(.title2)
.foregroundColor(.primary)
}
Divider()
}
}
.padding()
}
}
struct AssetSectionView_Previews: PreviewProvider {
static var previews: some View {
let asset = Asset(
id: 0,
type: .bankAccount,
data: [
AssetData(id: 0, title: "์ ํ์ํ", amount: "5,300,000์"),
AssetData(id: 1, title: "๊ตญ๋ฏผ์ํ", amount: "700,000์"),
AssetData(id: 2, title: "์นด์นด์ค๋ฑ
ํฌ", amount: "112,900,000์")
]
)
AssetSectionView(assetSection: asset)
}
}
๐ PageViewController.swift
//
// PageViewController.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
import UIKit
struct PageViewController<Page: View>: UIViewControllerRepresentable {
var pages: [Page]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[context.coordinator.controllers[currentPage]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
var controllers = [UIViewController]()
init(_ pageViewController: PageViewController) {
parent = pageViewController
controllers = parent.pages.map { UIHostingController(rootView: $0) }
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController) else { return nil }
if index == 0 {
return controllers.last
}
return controllers[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController) else { return nil }
if index + 1 == controllers.count {
return controllers.first
}
return controllers[index + 1]
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = controllers.firstIndex(of: visibleViewController) {
parent.currentPage = index
}
}
}
}
๐ PageControl.swift
//
// PageControl.swift
// MyAssets
//
// Created by Hamlit Jason on 2022/02/23.
//
import SwiftUI
import UIKit
struct PageControl: UIViewRepresentable {
var numberOfPages: Int
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
control.numberOfPages = numberOfPages
control.addTarget(
context.coordinator,
action: #selector(Coordinator.updateCurrentPage(sender:)),
for: .valueChanged
)
return control
}
func updateUIView(_ uiView: UIPageControl, context: Context) {
uiView.currentPage = currentPage
}
class Coordinator: NSObject {
var control: PageControl
init(_ control: PageControl) {
self.control = control
}
@objc func updateCurrentPage(sender: UIPageControl) {
control.currentPage = sender.currentPage
}
}
}
'โ ๏ธ deprecated โ ๏ธ > ํจ์บ (์ด๊ฒฉ์ฐจ)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
part5 (ch1). FindCVS UnitTest ์ฝ๋๋ฆฌ๋ทฐ (feat. Stubber) (0) | 2022.02.20 |
---|---|
part5 (ch1). FindCVS ์ฝ๋๋ฆฌ๋ทฐ (0) | 2022.02.20 |
part5 (ch6). KeywordNews XCTest ์ฝ๋๋ฆฌ๋ทฐ (0) | 2022.02.17 |
part5 (ch6). KeywordNews ์ฝ๋๋ฆฌ๋ทฐ (0) | 2022.02.15 |
part5 (ch6). ๐ช CI/CD๋? (feat. bitrise) (0) | 2022.02.15 |