From ac01fcf304cce21f3de3475b51c49adedf1cdbc1 Mon Sep 17 00:00:00 2001 From: heexohee Date: Thu, 21 Nov 2024 21:00:41 +0900 Subject: [PATCH 1/2] =?UTF-8?q?#92=20feat:=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EB=85=B8=ED=8B=B0=ED=94=BC=EC=BC=80=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=B0=8F=20=EC=98=88=EC=8B=9C=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- StepSquad/NotificationManager.swift | 250 ++++++++++++++++++ StepSquad/NotificationView.swift | 47 ++++ StepSquad/StepSquad.xcodeproj/project.pbxproj | 30 ++- 3 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 StepSquad/NotificationManager.swift create mode 100644 StepSquad/NotificationView.swift diff --git a/StepSquad/NotificationManager.swift b/StepSquad/NotificationManager.swift new file mode 100644 index 0000000..7e8ad9e --- /dev/null +++ b/StepSquad/NotificationManager.swift @@ -0,0 +1,250 @@ +// +// Notification.swift +// StepSquad +// +// Created by heesohee on 11/21/24. +// + + +//import Foundation +//import UserNotifications +//import CoreLocation +// +// +//class NotificationManager { +// +// static let shared = NotificationManager() +// +// // 사용자에게 노티피케이션 권한 요청 // .provisional는 +// func requestAuthorization() { +// let options:UNAuthorizationOptions = [.alert,.sound,.badge] +// UNUserNotificationCenter.current().requestAuthorization(options: options) { sucess, error in +// if let error = error{ +// print("Error: - \(error.localizedDescription)") +// } else { +// print("사용자에게서 알림 권한을 받는데 성공했습니다.") +// } +// } +// +// } +// +// +// // 노티피케이션 콘텐츠 +// func scheduleNotification(trigger:TriggerType){ +// let content = UNMutableNotificationContent() +// content.title = "78계단" +// content.body = "식후 계단 사용은 어때요?" +// content.sound = .default +// content.badge = 1 +// +// let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger.trigger) +// UNUserNotificationCenter.current().add(request) +// } +// +// // 노티피케이션 취소 함수 +// func cancelNotification(){ +// UNUserNotificationCenter.current().removeAllDeliveredNotifications() +// UNUserNotificationCenter.current().removeAllPendingNotificationRequests() +// } +//} +// +//// 노티피케이션 트리거 종류 3개 +//enum TriggerType: String{ +// case time = "time" +// case calender = "calender" +// case location = "location" +// +// var trigger:UNNotificationTrigger{ +// switch self { +// case .time: // 특정 시간 지난 후에 +// return UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) +// case .calender: // 특정 시간에 (월2, 화3, 수4, 목5, 금6, 토7) +// let dateComponent = DateComponents(hour: 20, minute: 22, weekday: 1) +// print("특정 시간에 노티") +// return UNCalendarNotificationTrigger(dateMatching: dateComponent, repeats: false) +// case .location: +// let coordinate = CLLocationCoordinate2D(latitude: 40.0, longitude: 50.0) +// let region = CLCircularRegion(center: coordinate, radius: 100, identifier: UUID().uuidString) +// return UNLocationNotificationTrigger(region: region, repeats: true) +// } +// } +//} +import SwiftUI +import UserNotifications + +class NotificationManager { + + static let instance = NotificationManager() // 인스턴스 생성 + + // MARK: - 사용자에게 노티피케이션 권한 요청 + func requestAuthorization() { + + // 노티피케이션 옵션들 + // To request provisional authorization, add the provisional option when requesting permission to send notifications. + let option: UNAuthorizationOptions = [.alert, .sound, .badge, .provisional] + + // UserNotification 접근 + UNUserNotificationCenter.current().requestAuthorization(options: option) { (success, error) in + if let error = error { + print("Error: - \(error.localizedDescription)") + } else { + print("사용자에게서 알림 권한을 받는데 성공했습니다.") + } + } + } + + // MARK: - 특정 시간에 노티를 주는 함수 + func scheduleNotification() { + + // notification 내용 설정 + let content = UNMutableNotificationContent() + content.title = "78계단" + content.subtitle = "앱 알람 테스트 중 입니다" + content.sound = .default + + + // 배지 값 증가 설정 + // UNUserNotificationCenter.current().getNotificationSettings { settings in + // if settings.authorizationStatus == .authorized { + // // 배지 값 가져와 증가 + // let currentBadgeCount = UIApplication.shared.applicationIconBadgeNumber + // UNUserNotificationCenter.current().setBadgeCount(currentBadgeCount + 1) { error in + // if let error = error { + // print("배지 설정 중 에러 발생: \(error.localizedDescription)") + // } + // } + // } + // } + // + content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + 1) + // content.badge = 1 + + // MARK: - 시간 기준 : Interval - 몇 초 뒤에 울릴것인지 딜레이 설정 repeats 반복 여부 설정 (최소 1분이여지 반복이 돔) + let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60.0, repeats: false) + + + // MARK: - 특정 시간에 노티를 주는 함수, 날짜 기준 : DateMating 은 DateComponent 기준맞는 알림 + var dateComponents = DateComponents() + dateComponents.hour = 2 // hour 를 24시간 기준 + dateComponents.minute = 00 + dateComponents.weekday = 2 // 1은 일요일이 되고, 6은 금요일이 됨 + + let calendarTigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) + + + // 설정한 값을 NotificationCenter 에 요청하기 + let request = UNNotificationRequest( + identifier: UUID().uuidString, // 각각의 request ID 값 String 을 uuid 값으로 설정 + content: content, + trigger: timeTrigger) + UNUserNotificationCenter.current().add(request) + + print("노티 설정 완료") + } + + // MARK: - 생성된 Notification Cancel 하는 함수 + func cancelNotification() { + + // peding notification 은 tigger 상에서 만족된 조건이 되어도 더이상 notification 되지 않게 하기 + UNUserNotificationCenter.current().removeAllPendingNotificationRequests() + print("노티 취소.") + + // 아이폰 상태바를 내렸을때 남아있는 notification 없애기 + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + print("노티 취소.") + } + + +} + + +// MARK: - 예시 뷰 +struct LocalNotificationInter: View { + + @Environment(\.scenePhase) var scenePhase + + var body: some View { + VStack (spacing: 40) { + + // MARK: - 사용자에게 노티피케이션 권한 요청 + Button { + NotificationManager.instance.requestAuthorization() + } label: { + Text("권한 요청하기") + .font(.title2) + .fontWeight(.semibold) + .foregroundColor(.black) + .padding() + .background(Color.green) + .cornerRadius(10) + } + + // MARK: - 특정 시간에 노티를 주는 함수(구간 반복) + Button { + NotificationManager.instance.scheduleNotification() + } label: { + Text("Time Notification") + .font(.title2) + .fontWeight(.semibold) + .foregroundColor(.black) + .padding() + .background(Color.green) + .cornerRadius(10) + } + + // MARK: - 특정 시간에 노티를 주는 함수 + Button { + NotificationManager.instance.scheduleNotification() + } label: { + Text("Calendar Notification") + .font(.title2) + .fontWeight(.semibold) + .foregroundColor(.black) + .padding() + .background(Color.green) + .cornerRadius(10) + } + + // MARK: - 노티 취소 함수 + Button { + NotificationManager.instance.cancelNotification() + } label: { + Text("Notification 취소하기") + .font(.title2) + .fontWeight(.semibold) + .foregroundColor(.black) + .padding() + .background(Color.green) + .cornerRadius(10) + } + + } //: VSTACK + // schne 이 나타 날때 Badge 0 으로 초기화 하기 + // Badge 초기화 + + .onChange(of: scenePhase) { newValue in + if newValue == .active { + UIApplication.shared.applicationIconBadgeNumber = 0 + } + } + } + } + +// .onChange(of: scenePhase) { _, _ in +// if scenePhase == .active { +// UNUserNotificationCenter.current().setBadgeCount(0) { error in +// if let error = error { +// print("Badge 초기화 중 에러 발생: \(error.localizedDescription)") +// } +// } +// } +// } +// } +//} + +struct LocalNotificationInter_Previews: PreviewProvider { + static var previews: some View { + LocalNotificationInter() + } +} + diff --git a/StepSquad/NotificationView.swift b/StepSquad/NotificationView.swift new file mode 100644 index 0000000..c4c8e08 --- /dev/null +++ b/StepSquad/NotificationView.swift @@ -0,0 +1,47 @@ +// +// NotificationView.swift +// StepSquad +// +// Created by heesohee on 11/21/24. +// + +import SwiftUI + +struct NotificationView: View { + var body: some View { + VStack(spacing: 20) { + // 권한 요청 버튼 + Button("노티피케이션 권한 요청") { + NotificationManager.instance.requestAuthorization() + } + .padding() + .background(Color.green) + .foregroundColor(.white) + .cornerRadius(10) + + // 특정 시간 노티 설정 버튼 + Button("특정 시간에 노티피케이션 설정") { + NotificationManager.instance.scheduleNotification() + } + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(10) + + // 노티피케이션 취소 버튼 + Button("노티피케이션 취소하기") { + NotificationManager.instance.cancelNotification() + } + .padding() + .background(Color.red) + .foregroundColor(.white) + .cornerRadius(10) + } + .padding() + } +} + + +#Preview { + NotificationView() +} diff --git a/StepSquad/StepSquad.xcodeproj/project.pbxproj b/StepSquad/StepSquad.xcodeproj/project.pbxproj index cf7b8a9..b9e7fa2 100644 --- a/StepSquad/StepSquad.xcodeproj/project.pbxproj +++ b/StepSquad/StepSquad.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 51C83F072CEF4B7C008E38E7 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C83F062CEF4B7C008E38E7 /* NotificationManager.swift */; }; + 51C83F092CEF4C95008E38E7 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C83F082CEF4C95008E38E7 /* NotificationView.swift */; }; BDD934652CDCB12300859EA4 /* Messages.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDD934642CDCB12300859EA4 /* Messages.framework */; }; BDD934712CDCB12400859EA4 /* StepSquadStickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = BDD934632CDCB12300859EA4 /* StepSquadStickers.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -37,6 +39,11 @@ /* Begin PBXFileReference section */ 1B997D832CD1B5F700F7CDC5 /* StepSquad.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StepSquad.entitlements; sourceTree = ""; }; + 51C83EB22CED7437008E38E7 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 51C83EB42CED7437008E38E7 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 51C83EC92CED7444008E38E7 /* StepSquadWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StepSquadWidgetExtension.entitlements; sourceTree = ""; }; + 51C83F062CEF4B7C008E38E7 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; + 51C83F082CEF4C95008E38E7 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; BDD309C22CBA62C3003EDC28 /* StepSquad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StepSquad.app; sourceTree = BUILT_PRODUCTS_DIR; }; BDD934632CDCB12300859EA4 /* StepSquadStickers.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = StepSquadStickers.appex; sourceTree = BUILT_PRODUCTS_DIR; }; BDD934642CDCB12300859EA4 /* Messages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Messages.framework; path = System/Library/Frameworks/Messages.framework; sourceTree = SDKROOT; }; @@ -54,6 +61,11 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 51C83EB62CED7437008E38E7 /* StepSquadWidget */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = StepSquadWidget; + sourceTree = ""; + }; BDD309C42CBA62C3003EDC28 /* StepSquad */ = { isa = PBXFileSystemSynchronizedRootGroup; path = StepSquad; @@ -91,10 +103,14 @@ BDD309B92CBA62C3003EDC28 = { isa = PBXGroup; children = ( + 51C83F062CEF4B7C008E38E7 /* NotificationManager.swift */, + 51C83F082CEF4C95008E38E7 /* NotificationView.swift */, + 51C83EC92CED7444008E38E7 /* StepSquadWidgetExtension.entitlements */, BDECE49C2CD35A8700AD6229 /* StepSquad-Info.plist */, 1B997D832CD1B5F700F7CDC5 /* StepSquad.entitlements */, BDD309C42CBA62C3003EDC28 /* StepSquad */, BDD934662CDCB12300859EA4 /* StepSquadStickers */, + 51C83EB62CED7437008E38E7 /* StepSquadWidget */, BDD30A092CBA6387003EDC28 /* Frameworks */, BDD309C32CBA62C3003EDC28 /* Products */, ); @@ -113,6 +129,8 @@ isa = PBXGroup; children = ( BDD934642CDCB12300859EA4 /* Messages.framework */, + 51C83EB22CED7437008E38E7 /* WidgetKit.framework */, + 51C83EB42CED7437008E38E7 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -173,8 +191,8 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1600; - LastUpgradeCheck = 1600; + LastSwiftUpdateCheck = 1610; + LastUpgradeCheck = 1610; TargetAttributes = { BDD309C12CBA62C3003EDC28 = { CreatedOnToolsVersion = 16.0; @@ -227,6 +245,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51C83F072CEF4B7C008E38E7 /* NotificationManager.swift in Sources */, + 51C83F092CEF4C95008E38E7 /* NotificationView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -253,6 +273,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -316,6 +337,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -402,7 +424,7 @@ PRODUCT_BUNDLE_IDENTIFIER = kr.45SeekDance.2024; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = stapSquadApp; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = stepSquadApp; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -448,7 +470,7 @@ PRODUCT_BUNDLE_IDENTIFIER = kr.45SeekDance.2024; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = stapSquadApp; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = stepSquadApp; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From aa765ecb914f932452863a7143d15e2a2f957adb Mon Sep 17 00:00:00 2001 From: heexohee Date: Thu, 21 Nov 2024 21:02:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?#92=20chore:=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- StepSquad/NotificationManager.swift | 66 ++++++++++++++--------------- StepSquad/NotificationView.swift | 4 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/StepSquad/NotificationManager.swift b/StepSquad/NotificationManager.swift index 7e8ad9e..b260989 100644 --- a/StepSquad/NotificationManager.swift +++ b/StepSquad/NotificationManager.swift @@ -73,16 +73,16 @@ import SwiftUI import UserNotifications class NotificationManager { - + static let instance = NotificationManager() // 인스턴스 생성 - + // MARK: - 사용자에게 노티피케이션 권한 요청 func requestAuthorization() { - + // 노티피케이션 옵션들 // To request provisional authorization, add the provisional option when requesting permission to send notifications. let option: UNAuthorizationOptions = [.alert, .sound, .badge, .provisional] - + // UserNotification 접근 UNUserNotificationCenter.current().requestAuthorization(options: option) { (success, error) in if let error = error { @@ -92,17 +92,17 @@ class NotificationManager { } } } - + // MARK: - 특정 시간에 노티를 주는 함수 func scheduleNotification() { - + // notification 내용 설정 let content = UNMutableNotificationContent() content.title = "78계단" content.subtitle = "앱 알람 테스트 중 입니다" content.sound = .default - - + + // 배지 값 증가 설정 // UNUserNotificationCenter.current().getNotificationSettings { settings in // if settings.authorizationStatus == .authorized { @@ -118,54 +118,54 @@ class NotificationManager { // content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + 1) // content.badge = 1 - + // MARK: - 시간 기준 : Interval - 몇 초 뒤에 울릴것인지 딜레이 설정 repeats 반복 여부 설정 (최소 1분이여지 반복이 돔) let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60.0, repeats: false) - - + + // MARK: - 특정 시간에 노티를 주는 함수, 날짜 기준 : DateMating 은 DateComponent 기준맞는 알림 var dateComponents = DateComponents() dateComponents.hour = 2 // hour 를 24시간 기준 dateComponents.minute = 00 dateComponents.weekday = 2 // 1은 일요일이 되고, 6은 금요일이 됨 - + let calendarTigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) - - + + // 설정한 값을 NotificationCenter 에 요청하기 let request = UNNotificationRequest( identifier: UUID().uuidString, // 각각의 request ID 값 String 을 uuid 값으로 설정 content: content, trigger: timeTrigger) UNUserNotificationCenter.current().add(request) - + print("노티 설정 완료") } - + // MARK: - 생성된 Notification Cancel 하는 함수 func cancelNotification() { - + // peding notification 은 tigger 상에서 만족된 조건이 되어도 더이상 notification 되지 않게 하기 UNUserNotificationCenter.current().removeAllPendingNotificationRequests() print("노티 취소.") - + // 아이폰 상태바를 내렸을때 남아있는 notification 없애기 UNUserNotificationCenter.current().removeAllDeliveredNotifications() print("노티 취소.") } - - + + } // MARK: - 예시 뷰 struct LocalNotificationInter: View { - + @Environment(\.scenePhase) var scenePhase - + var body: some View { VStack (spacing: 40) { - + // MARK: - 사용자에게 노티피케이션 권한 요청 Button { NotificationManager.instance.requestAuthorization() @@ -178,7 +178,7 @@ struct LocalNotificationInter: View { .background(Color.green) .cornerRadius(10) } - + // MARK: - 특정 시간에 노티를 주는 함수(구간 반복) Button { NotificationManager.instance.scheduleNotification() @@ -191,7 +191,7 @@ struct LocalNotificationInter: View { .background(Color.green) .cornerRadius(10) } - + // MARK: - 특정 시간에 노티를 주는 함수 Button { NotificationManager.instance.scheduleNotification() @@ -204,7 +204,7 @@ struct LocalNotificationInter: View { .background(Color.green) .cornerRadius(10) } - + // MARK: - 노티 취소 함수 Button { NotificationManager.instance.cancelNotification() @@ -217,18 +217,18 @@ struct LocalNotificationInter: View { .background(Color.green) .cornerRadius(10) } - + } //: VSTACK // schne 이 나타 날때 Badge 0 으로 초기화 하기 // Badge 초기화 - - .onChange(of: scenePhase) { newValue in - if newValue == .active { - UIApplication.shared.applicationIconBadgeNumber = 0 - } - } + + .onChange(of: scenePhase) { newValue in + if newValue == .active { + UIApplication.shared.applicationIconBadgeNumber = 0 } } + } +} // .onChange(of: scenePhase) { _, _ in // if scenePhase == .active { diff --git a/StepSquad/NotificationView.swift b/StepSquad/NotificationView.swift index c4c8e08..6fe4305 100644 --- a/StepSquad/NotificationView.swift +++ b/StepSquad/NotificationView.swift @@ -18,7 +18,7 @@ struct NotificationView: View { .background(Color.green) .foregroundColor(.white) .cornerRadius(10) - + // 특정 시간 노티 설정 버튼 Button("특정 시간에 노티피케이션 설정") { NotificationManager.instance.scheduleNotification() @@ -27,7 +27,7 @@ struct NotificationView: View { .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) - + // 노티피케이션 취소 버튼 Button("노티피케이션 취소하기") { NotificationManager.instance.cancelNotification()