Skip to content

Commit 5065089

Browse files
committed
[#249] Toast 수정 및 Haptic 추가
1 parent 3cb76ff commit 5065089

File tree

4 files changed

+111
-32
lines changed

4 files changed

+111
-32
lines changed

Diff for: YDS-Essential/Source/HapticManager.swift

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// HapticManager.swift
3+
// YDS
4+
//
5+
// Created by 정종인 on 12/25/23.
6+
//
7+
8+
import UIKit
9+
10+
public class HapticManager {
11+
public static let instance = HapticManager()
12+
private init() {}
13+
14+
public func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
15+
let generator = UINotificationFeedbackGenerator()
16+
generator.notificationOccurred(type)
17+
}
18+
19+
public func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) {
20+
let generator = UIImpactFeedbackGenerator(style: style)
21+
generator.impactOccurred()
22+
}
23+
}
24+
25+
/*
26+
Haptic Manager 사용 방법
27+
HapticManager.instance.notification(type: .success)
28+
HapticManager.instance.impact(style: .soft)
29+
*/

Diff for: YDS-Storybook/SwiftUI/Components/ToastPageView.swift

+9-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct ToastPageView: View {
1313

1414
@State var text: String? = "Toast"
1515
@State var durationIndex: Int = 0
16-
@State var isShowing: Bool = false
16+
@State var hapticIndex: Int = 0
1717

1818
var body: some View {
1919
StorybookPageView(
@@ -26,21 +26,26 @@ struct ToastPageView: View {
2626
),
2727
Option.enum(
2828
description: "duration",
29-
cases: YDSToast.ToastDuration.allCases,
29+
cases: YDSToastModel.ToastDuration.allCases,
3030
selectedIndex: $durationIndex
31+
),
32+
Option.enum(
33+
description: "haptic",
34+
cases: YDSToastModel.HapticType.allCases,
35+
selectedIndex: $hapticIndex
3136
)
3237
]
3338
)
3439
.navigationTitle(title)
3540
.overlay(alignment: .bottom) {
3641
Button(action: { // 버튼은 추후 YDSButton 추가 이후에 수정 예정
37-
isShowing = true
42+
YDSToast(text ?? "", duration: .allCases[durationIndex], haptic: .allCases[hapticIndex])
3843
}, label: {
3944
Text("토스트 생성!")
4045
})
4146
.padding()
4247
}
43-
.ydsToast($text, isShowing: $isShowing)
48+
.registerYDSToast()
4449
}
4550
}
4651

Diff for: YDS-SwiftUI/Source/Component/YDSToast.swift

+69-28
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88
import SwiftUI
99
import UIKit
1010
import YDS_Essential
11+
import Combine
12+
/*
13+
YDSToast 사용 방법
14+
1. 최상단 뷰에 .registerYDSToast() Modifier를 붙여준다.
15+
2. toast를 띄우고 싶을 때 YDSToast()를 불러준다.
16+
*/
1117

12-
public struct YDSToast: Equatable {
18+
public struct YDSToastModel: Equatable {
1319
let text: String
1420
let duration: ToastDuration
21+
let haptic: HapticType
1522
public enum ToastDuration: CaseIterable {
1623
case short
1724
case long
@@ -25,6 +32,30 @@ public struct YDSToast: Equatable {
2532
}
2633
}
2734
}
35+
public enum HapticType: CaseIterable {
36+
case success
37+
case failed
38+
case none
39+
}
40+
var playHaptic: () -> Void {
41+
switch haptic {
42+
case .success: {
43+
HapticManager.instance.notification(type: .success)
44+
}
45+
case .failed: {
46+
HapticManager.instance.notification(type: .warning)
47+
}
48+
case .none: {}
49+
}
50+
}
51+
}
52+
53+
public func YDSToast(
54+
_ text: String,
55+
duration: YDSToastModel.ToastDuration = .short,
56+
haptic: YDSToastModel.HapticType = .none
57+
) {
58+
YDSToastHelper.shared.enqueueToast(YDSToastModel(text: text, duration: duration, haptic: haptic))
2859
}
2960

3061
public struct YDSToastView: View {
@@ -44,43 +75,53 @@ public struct YDSToastView: View {
4475
}
4576
}
4677

47-
public struct YDSToastModifier: ViewModifier {
48-
@Binding public var isShowing: Bool
49-
@Binding public var toast: YDSToast
50-
public func body(content: Content) -> some View {
78+
private class YDSToastHelper: ObservableObject {
79+
static let shared = YDSToastHelper()
80+
@Published var isShowing: Bool = false
81+
@Published var showingToast: YDSToastModel?
82+
private let subject = PassthroughSubject<YDSToastModel, Never>()
83+
private var cancellables = Set<AnyCancellable>()
84+
init() {
85+
initSubscription()
86+
}
87+
private func initSubscription() {
88+
subject
89+
.receive(on: RunLoop.main)
90+
.sink { [weak self] toast in
91+
self?.isShowing = false
92+
self?.isShowing = true
93+
self?.showingToast = toast
94+
toast.playHaptic()
95+
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration.value) {
96+
self?.isShowing = false
97+
self?.showingToast = nil
98+
}
99+
}
100+
.store(in: &cancellables)
101+
}
102+
func enqueueToast(_ toast: YDSToastModel) {
103+
subject.send(toast)
104+
}
105+
}
106+
107+
struct YDSToastModifier: ViewModifier {
108+
@StateObject private var toastHelper = YDSToastHelper.shared
109+
func body(content: Content) -> some View {
51110
content
52111
.overlay(alignment: .bottom) {
53-
if isShowing {
112+
if toastHelper.isShowing, let toast = toastHelper.showingToast {
54113
YDSToastView(toast.text)
55-
.opacity(isShowing ? 1.0 : 0.0)
56-
}
57-
}
58-
.onChange(of: isShowing) { value in
59-
if value {
60-
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration.value) {
61-
isShowing = false
62-
}
114+
.opacity(toastHelper.isShowing ? 1.0 : 0.0)
63115
}
64116
}
65-
.animation(.easeInOut, value: isShowing)
117+
.animation(.easeInOut, value: toastHelper.isShowing)
66118
}
67119
}
68120

69121
extension View {
70-
public func ydsToast(
71-
_ text: Binding<String?>,
72-
duration: YDSToast.ToastDuration = .short,
73-
isShowing: Binding<Bool>
74-
) -> some View {
122+
public func registerYDSToast() -> some View {
75123
self.modifier(
76-
YDSToastModifier(
77-
isShowing: isShowing,
78-
toast: .init(get: {
79-
YDSToast(text: text.wrappedValue ?? "", duration: duration)
80-
}, set: { ydsToast in
81-
text.wrappedValue = ydsToast.text
82-
})
83-
)
124+
YDSToastModifier()
84125
)
85126
}
86127
}

Diff for: YDS.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
5A10B1B62A8F5C8500139E89 /* SwiftUIYDSIconArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A10B1B52A8F5C8500139E89 /* SwiftUIYDSIconArray.swift */; };
9191
5A10B1B82A8F5FFF00139E89 /* IconPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A10B1B72A8F5FFF00139E89 /* IconPageView.swift */; };
9292
6CA05E822A90846B00B07920 /* SwiftUIYDSColorArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA05E812A90846B00B07920 /* SwiftUIYDSColorArray.swift */; };
93+
6F124C632B399953004187E6 /* HapticManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F124C612B399753004187E6 /* HapticManager.swift */; };
9394
6F2D527B2A79446000BAF200 /* ColorPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2D527A2A79446000BAF200 /* ColorPageView.swift */; };
9495
6F2D527D2A7944D800BAF200 /* PageListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2D527C2A7944D800BAF200 /* PageListView.swift */; };
9596
6F3C1EEE2A7A8573003D0D06 /* YDS_Essential.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F3C1EED2A7A8573003D0D06 /* YDS_Essential.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -329,6 +330,7 @@
329330
5A10B1B52A8F5C8500139E89 /* SwiftUIYDSIconArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIYDSIconArray.swift; sourceTree = "<group>"; };
330331
5A10B1B72A8F5FFF00139E89 /* IconPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPageView.swift; sourceTree = "<group>"; };
331332
6CA05E812A90846B00B07920 /* SwiftUIYDSColorArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIYDSColorArray.swift; sourceTree = "<group>"; };
333+
6F124C612B399753004187E6 /* HapticManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticManager.swift; sourceTree = "<group>"; };
332334
6F2D527A2A79446000BAF200 /* ColorPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPageView.swift; sourceTree = "<group>"; };
333335
6F2D527C2A7944D800BAF200 /* PageListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListView.swift; sourceTree = "<group>"; };
334336
6F3C1EEB2A7A8572003D0D06 /* YDS_Essential.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YDS_Essential.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -776,6 +778,7 @@
776778
6F3C1F132A7A9616003D0D06 /* Source */ = {
777779
isa = PBXGroup;
778780
children = (
781+
6F124C612B399753004187E6 /* HapticManager.swift */,
779782
6F95FE5B2A768CAF00B398CF /* YDSBundle.swift */,
780783
6F3C1EFF2A7A8843003D0D06 /* Rule */,
781784
6F3C1EFE2A7A8839003D0D06 /* Foundation */,
@@ -1343,6 +1346,7 @@
13431346
buildActionMask = 2147483647;
13441347
files = (
13451348
6F3C1F122A7A9428003D0D06 /* YDSConstant.swift in Sources */,
1349+
6F124C632B399953004187E6 /* HapticManager.swift in Sources */,
13461350
6F3C1F0C2A7A8EC6003D0D06 /* YDSBundle.swift in Sources */,
13471351
6F3C1F112A7A9428003D0D06 /* YDSAnimation.swift in Sources */,
13481352
6F3C1F102A7A9428003D0D06 /* YDSScreenSize.swift in Sources */,

0 commit comments

Comments
 (0)