Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#249] Toast Duration 수정 및 Haptic 추가 #253

Merged
merged 1 commit into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions YDS-Essential/Source/HapticManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// HapticManager.swift
// YDS
//
// Created by 정종인 on 12/25/23.
//

import UIKit

public class HapticManager {
public static let instance = HapticManager()
private init() {}

public func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(type)
}

public func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) {
let generator = UIImpactFeedbackGenerator(style: style)
generator.impactOccurred()
}
}

/*
Haptic Manager 사용 방법
HapticManager.instance.notification(type: .success)
HapticManager.instance.impact(style: .soft)
*/
13 changes: 9 additions & 4 deletions YDS-Storybook/SwiftUI/Components/ToastPageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct ToastPageView: View {

@State var text: String? = "Toast"
@State var durationIndex: Int = 0
@State var isShowing: Bool = false
@State var hapticIndex: Int = 0

var body: some View {
StorybookPageView(
Expand All @@ -26,21 +26,26 @@ struct ToastPageView: View {
),
Option.enum(
description: "duration",
cases: YDSToast.ToastDuration.allCases,
cases: YDSToastModel.ToastDuration.allCases,
selectedIndex: $durationIndex
),
Option.enum(
description: "haptic",
cases: YDSToastModel.HapticType.allCases,
selectedIndex: $hapticIndex
)
]
)
.navigationTitle(title)
.overlay(alignment: .bottom) {
Button(action: { // 버튼은 추후 YDSButton 추가 이후에 수정 예정
isShowing = true
YDSToast(text ?? "", duration: .allCases[durationIndex], haptic: .allCases[hapticIndex])
}, label: {
Text("토스트 생성!")
})
.padding()
}
.ydsToast($text, isShowing: $isShowing)
.registerYDSToast()
}
}

Expand Down
97 changes: 69 additions & 28 deletions YDS-SwiftUI/Source/Component/YDSToast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@
import SwiftUI
import UIKit
import YDS_Essential
import Combine
/*
YDSToast 사용 방법
1. 최상단 뷰에 .registerYDSToast() Modifier를 붙여준다.
2. toast를 띄우고 싶을 때 YDSToast()를 불러준다.
*/

public struct YDSToast: Equatable {
public struct YDSToastModel: Equatable {
let text: String
let duration: ToastDuration
let haptic: HapticType
public enum ToastDuration: CaseIterable {
case short
case long
Expand All @@ -25,6 +32,30 @@ public struct YDSToast: Equatable {
}
}
}
public enum HapticType: CaseIterable {
case success
case failed
case none
}
var playHaptic: () -> Void {
switch haptic {
case .success: {
HapticManager.instance.notification(type: .success)
}
case .failed: {
HapticManager.instance.notification(type: .warning)
}
case .none: {}
}
}
}

public func YDSToast(
_ text: String,
duration: YDSToastModel.ToastDuration = .short,
haptic: YDSToastModel.HapticType = .none
) {
YDSToastHelper.shared.enqueueToast(YDSToastModel(text: text, duration: duration, haptic: haptic))
}

public struct YDSToastView: View {
Expand All @@ -44,43 +75,53 @@ public struct YDSToastView: View {
}
}

public struct YDSToastModifier: ViewModifier {
@Binding public var isShowing: Bool
@Binding public var toast: YDSToast
public func body(content: Content) -> some View {
private class YDSToastHelper: ObservableObject {
static let shared = YDSToastHelper()
@Published var isShowing: Bool = false
@Published var showingToast: YDSToastModel?
private let subject = PassthroughSubject<YDSToastModel, Never>()
private var cancellables = Set<AnyCancellable>()
init() {
initSubscription()
}
private func initSubscription() {
subject
.receive(on: RunLoop.main)
.sink { [weak self] toast in
self?.isShowing = false
self?.isShowing = true
self?.showingToast = toast
toast.playHaptic()
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration.value) {
self?.isShowing = false
self?.showingToast = nil
}
}
.store(in: &cancellables)
}
func enqueueToast(_ toast: YDSToastModel) {
subject.send(toast)
}
}

struct YDSToastModifier: ViewModifier {
@StateObject private var toastHelper = YDSToastHelper.shared
func body(content: Content) -> some View {
content
.overlay(alignment: .bottom) {
if isShowing {
if toastHelper.isShowing, let toast = toastHelper.showingToast {
YDSToastView(toast.text)
.opacity(isShowing ? 1.0 : 0.0)
}
}
.onChange(of: isShowing) { value in
if value {
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration.value) {
isShowing = false
}
.opacity(toastHelper.isShowing ? 1.0 : 0.0)
}
}
.animation(.easeInOut, value: isShowing)
.animation(.easeInOut, value: toastHelper.isShowing)
}
}

extension View {
public func ydsToast(
_ text: Binding<String?>,
duration: YDSToast.ToastDuration = .short,
isShowing: Binding<Bool>
) -> some View {
public func registerYDSToast() -> some View {
self.modifier(
YDSToastModifier(
isShowing: isShowing,
toast: .init(get: {
YDSToast(text: text.wrappedValue ?? "", duration: duration)
}, set: { ydsToast in
text.wrappedValue = ydsToast.text
})
)
YDSToastModifier()
)
}
}
4 changes: 4 additions & 0 deletions YDS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
5A10B1B62A8F5C8500139E89 /* SwiftUIYDSIconArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A10B1B52A8F5C8500139E89 /* SwiftUIYDSIconArray.swift */; };
5A10B1B82A8F5FFF00139E89 /* IconPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A10B1B72A8F5FFF00139E89 /* IconPageView.swift */; };
6CA05E822A90846B00B07920 /* SwiftUIYDSColorArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA05E812A90846B00B07920 /* SwiftUIYDSColorArray.swift */; };
6F124C632B399953004187E6 /* HapticManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F124C612B399753004187E6 /* HapticManager.swift */; };
6F2D527B2A79446000BAF200 /* ColorPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2D527A2A79446000BAF200 /* ColorPageView.swift */; };
6F2D527D2A7944D800BAF200 /* PageListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2D527C2A7944D800BAF200 /* PageListView.swift */; };
6F3C1EEE2A7A8573003D0D06 /* YDS_Essential.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F3C1EED2A7A8573003D0D06 /* YDS_Essential.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -329,6 +330,7 @@
5A10B1B52A8F5C8500139E89 /* SwiftUIYDSIconArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIYDSIconArray.swift; sourceTree = "<group>"; };
5A10B1B72A8F5FFF00139E89 /* IconPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPageView.swift; sourceTree = "<group>"; };
6CA05E812A90846B00B07920 /* SwiftUIYDSColorArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIYDSColorArray.swift; sourceTree = "<group>"; };
6F124C612B399753004187E6 /* HapticManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticManager.swift; sourceTree = "<group>"; };
6F2D527A2A79446000BAF200 /* ColorPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPageView.swift; sourceTree = "<group>"; };
6F2D527C2A7944D800BAF200 /* PageListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListView.swift; sourceTree = "<group>"; };
6F3C1EEB2A7A8572003D0D06 /* YDS_Essential.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YDS_Essential.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -776,6 +778,7 @@
6F3C1F132A7A9616003D0D06 /* Source */ = {
isa = PBXGroup;
children = (
6F124C612B399753004187E6 /* HapticManager.swift */,
6F95FE5B2A768CAF00B398CF /* YDSBundle.swift */,
6F3C1EFF2A7A8843003D0D06 /* Rule */,
6F3C1EFE2A7A8839003D0D06 /* Foundation */,
Expand Down Expand Up @@ -1343,6 +1346,7 @@
buildActionMask = 2147483647;
files = (
6F3C1F122A7A9428003D0D06 /* YDSConstant.swift in Sources */,
6F124C632B399953004187E6 /* HapticManager.swift in Sources */,
6F3C1F0C2A7A8EC6003D0D06 /* YDSBundle.swift in Sources */,
6F3C1F112A7A9428003D0D06 /* YDSAnimation.swift in Sources */,
6F3C1F102A7A9428003D0D06 /* YDSScreenSize.swift in Sources */,
Expand Down