앱으로 실행과 동의화면까지는 잘 가고 이후 액션이 없다고 하셨는데 카카오가 깔려있지 않아서 이메일, 비밀번호를 칠때는 로그인이 잘됩니다.
아래는 KakaoSignInButton 버튼 소스코드입니다.
//
// KakaoSignInButton.swift
// camp
//
// Created by minsekim on 12/1/23.
//
import Foundation
import SwiftUI
import KakaoSDKCommon
import KakaoSDKAuth
import KakaoSDKUser
import AuthenticationServices
import Amplitude
struct KakaoSignInButton: View {
@ObservedObject var appState = AppState.shared
var body: some View {
Button(action: kakaoLogin) {
HStack {
Image("KakaoLogin") // 카카오 로고 이미지
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
Text("카카오로 3초만에 시작하기")
.foregroundColor(.black)
.font(.custom("Pretendard", size: 15))
}
.padding(.leading, 40)
.padding(.trailing, 40)
.padding()
.background(Color(hex: "FFE94E"))
.cornerRadius(10)
}
}
func fetchMarketingAgreement(_ accessToken: String?, completion: @escaping (Bool) -> Void) {
guard let token = accessToken else {
print("AccessToken is not available")
return
}
let url = URL(string: "https://kapi.kakao.com/v1/user/service/terms")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
print("Starting fetchMarketingAgreement request")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil, let data = data else {
print("Error: \(error?.localizedDescription ?? "Unknown error")")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let terms = jsonResult["allowed_service_terms"] as? [[String: Any]] {
let isMktAgree = terms.contains { $0["tag"] as? String == "mkt_agree" }
completion(isMktAgree)
}
} catch {
print("JSON 파싱 에러: \(error)")
}
print("fetchMarketingAgreement request completed")
}
task.resume()
}
func kakaoLogin() {
if UserApi.isKakaoTalkLoginAvailable() {
UserApi.shared.loginWithKakaoTalk { (oauthToken, error) in
if let error = error {
print("1 로그인 실패: \(error)")
} else if let token = oauthToken {
print("AccessToken: \(token.accessToken)") // 로그 추가
self.getUserInfo(accessToken: token.accessToken)
} else {
print("1 로그인 실패(No token): \(String(describing: error))")
}
}
} else {
UserApi.shared.loginWithKakaoAccount { (oauthToken, error) in
if let error = error {
print("2 로그인 실패: \(error)")
} else if let token = oauthToken {
print("AccessToken: \(token.accessToken)") // 로그 추가
self.getUserInfo(accessToken: token.accessToken)
} else {
print("2 로그인 실패(No token): \(String(describing: error))")
}
}
}
}
func getUserInfo(accessToken: String?) {
UserApi.shared.me() { (user, error) in
if let error = error {
print("사용자 정보 요청 실패: \(error)")
} else if let user = user {
// 옵셔널 바인딩을 사용하여 user.id의 nil 체크를 합니다.
if let kakaoID = user.id {
let birthdate = "\(user.kakaoAccount?.birthyear ?? "0000")\(user.kakaoAccount?.birthday ?? "0000")"
sendUserInfoToServer(
kakaoID: kakaoID,
name: user.kakaoAccount?.name,
birthdate: birthdate,
phone: user.kakaoAccount?.phoneNumber,
email: user.kakaoAccount?.email,
gender: user.kakaoAccount?.gender?.rawValue == "male" ? "MALE" : "FEMALE",
profileImage: user.kakaoAccount?.profile?.profileImageUrl?.absoluteString,
accessToken: accessToken
)
} else {
print("카카오 사용자 ID가 존재하지 않습니다.")
}
}
}
}
func sendUserInfoToServer(kakaoID: Int64, name: String?, birthdate: String, phone: String?, email: String?, gender: String?, profileImage: String?, accessToken: String?) {
print(-2)
fetchMarketingAgreement(accessToken) { isMktAgree in
print(-1)
let url = URL(string: "https://node5.yagiyagi.kr/user/kakao")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(AuthorizationKeyGenerator.generate())", forHTTPHeaderField: "Authorization") // 인증 키를 헤더에 추가
let body: [String: Any?] = [
"kakao_id": kakaoID,
"name": name,
"birthdate": birthdate,
"phone": phone,
"email": email,
"gender": gender,
"profile_image": profileImage,
"is_mkt_agree":isMktAgree
]
print(0)
request.httpBody = try? JSONSerialization.data(withJSONObject: body.compactMapValues { $0 }, options: [])
print(1)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
return
}
print(2)
if let data = data {
do {
print(3)
if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let dataResponse = jsonResponse["data"] as? [String: Any],
let userID = dataResponse["user_id"] as? Int {
print(4)
DispatchQueue.main.async {
// UserDefaults에 user_id 저장
UserDefaults.standard.set(userID, forKey: "userId")
print(5)
print("회원가입/로그인 성공, User ID: \(userID) 저장됨")
appState.isUserLoggedIn = true
Amplitude.instance().setUserId(String("\(userID)"))
// 화면 전환 로직
// 먼저 웹 뷰 스택을 비운다
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.stackedWebViews = []
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
appState.stackedWebViews = [AppState.BASE_URL]
}
appState.isLoading = true
saveFcmToken()
// 예를 들어 웹뷰 화면으로 이동하는 로직 추가
}
} else {
print("서버 응답 파싱 실패")
}
} catch {
print("JSON 파싱 에러: \(error)")
}
}
}.resume()
}
}
}
extension Color {
init(hex: String) {
let scanner = Scanner(string: hex)
_ = scanner.scanString("#")
var rgb: UInt64 = 0
scanner.scanHexInt64(&rgb)
let r = Double((rgb >> 16) & 0xFF) / 255.0
let g = Double((rgb >> 8) & 0xFF) / 255.0
let b = Double(rgb & 0xFF) / 255.0
self.init(red: r, green: g, blue: b)
}
}
아래는 시작 view입니다.
MainView.swift
@main
struct CampApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
init() {
// Kakao SDK 초기화
KakaoSDK.initSDK(appKey: "207...99")
}
var body: some Scene {
WindowGroup {
SplashScreenView()
}
}
}
아래는 AppDelegate.swift입니다.
//
// AppDelegate.swift
// camp
//
// Created by minsekim on 12/26/23.
//
import Foundation
import SwiftUI
import FirebaseCore
import FirebaseMessaging
import UserNotifications
import Amplitude
class AppDelegate: NSObject, UIApplicationDelegate {
@ObservedObject var appState = AppState.shared
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// 'link' 키의 URL을 처리
if let link = userInfo["link"] as? String {
nativePush(path: link)
}
completionHandler()
}
private func nativePush(path: String) {
// AppState 인스턴스에 접근하기 위해 수정된 부분
if var urlComponents = URLComponents(url: AppState.BASE_URL, resolvingAgainstBaseURL: true) {
// path는 옵셔널이 아니므로 직접 접근 가능
let slicePath = path.dropFirst()
urlComponents.path += slicePath
print("urlComponents.path + url: "+String("\(urlComponents.path)"))
if let finalUrl = urlComponents.url {
DispatchQueue.main.async {
self.appState.stackedWebViews.append(finalUrl)
}
}
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
Amplitude.instance().initializeApiKey("19...8d")
Amplitude.instance().defaultTracking.sessions = true
let userId = UserDefaults.standard.string(forKey: "userId") ?? "null"
if(userId != "null"){
Amplitude.instance().setUserId(userId)
}
// 파이어베이스 설정
FirebaseApp.configure()
// 앱 실행 시 사용자에게 알림 허용 권한을 받음
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // 필요한 알림 권한을 설정
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
// UNUserNotificationCenterDelegate를 구현한 메서드를 실행시킴
application.registerForRemoteNotifications()
// 파이어베이스 Meesaging 설정
Messaging.messaging().delegate = self
return true
}
//앱이 화면에 켜져있는 상태에서도 push notification을 받고 싶다면 아래 메서드를 꼭 추가해주세요!
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.list, .banner])
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
// 백그라운드에서 푸시 알림을 탭했을 때 실행
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("APNS token: \(deviceToken)")
Messaging.messaging().apnsToken = deviceToken
}
// Foreground(앱 켜진 상태)에서도 알림 오는 설정
// func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// completionHandler([.list, .banner])
// }
}
extension AppDelegate: MessagingDelegate {
// 파이어베이스 MessagingDelegate 설정
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Firebase registration token: \(String(describing: fcmToken))")
let dataDict: [String: String] = ["token": fcmToken ?? ""]
saveFcmToken()
NotificationCenter.default.post(
name: Notification.Name("FCMToken"),
object: nil,
userInfo: dataDict
)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
}
func saveFcmToken() {
print("saveFcmToken")
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM token: \(error)")
} else if let token = token {
print("FCM Token: \(token)")
// 서버에 토큰 전송
guard let userId = UserDefaults.standard.string(forKey: "userId") else {
print("User ID not found")
sendTokenToServer(userId: "0", token: token)
return
}
sendTokenToServer(userId: userId, token: token)
}
}
}
func sendTokenToServer(userId: String, token: String) {
print("sendTokenToServer")
let url = URL(string: "백엔드주소")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(AuthorizationKeyGenerator.generate())", forHTTPHeaderField: "Authorization") // 인증 키를 헤더에 추가
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = ["user_id": userId, "token": token]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error sending token to server: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
print("Token successfully sent to server")
} else {
print("Failed to send token to server")
}
}.resume()
}
추가로,
1.api error: ClientFailed(reason: KakaoSDKCommon.ClientFailureReason.TokenNotFound, errorMessage: Optional(“authentication tokens not exist.”))
해당 에러도 앱으로 돌아왔을때 처리가 없을때랑 관련있는것인지 확인 부탁드려요.