SwiftUIアプリにLINEログインを追加、ユーザープロファイル情報の取得（.onOpenURLモディファイアを使用）

この記事では、SwiftUIアプリでLineログインを統合し、ログイン成功時にユーザーのプロファイル情報を取得することについて説明します。

私は新しいChatGPT iOSアプリ（興味があれば私のTwitterをチェックしてください）でLineログイン機能を実装していたのですが、SwiftUIアプリ用のインストラクションがないことを発見しました。そこで、この記事を書きました。

<img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/e54b687e-6152-8361-6758-732859794bfb.png">

## SwiftUIアプリとは

SwiftUIアプリは`App`構造体のみを持ち、`AppDelegate` ファイルや `SceneDelegate` ファイルは持っていません。

```swift
import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

認証に成功した後、
LineはアプリのカスタムURLスキーマを呼び出し、
認証トークンの情報を提供します。

SwiftUIアプリでは、
`onOpenURL`モディファイアがあり、
ユーザーがLineで認証した後のログイン結果を処理するために使用することができます。

## アプリの登録

まず、LINEのデベロッパーサイトにアクセスし、アプリケーションを登録します。

https://developers.line.biz/console/

まず、プロバイダを作成します。
これは会社名でも自分の名前でもかまいません。
プロバイダーは複数のチャンネル（アプリケーション）を含むことができます。

![Screenshot 2023-03-24 at 15.07.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/975c1732-f6e2-3c43-56a0-9b154f216a5a.png)

次に、作成したプロバイダーをクリックし、アプリケーション（チャネル）を作成します。
種類は、"LINEログイン"を選択します。

![Screenshot 2023-03-24 at 15.11.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/0fd294d7-95ea-6dcd-739c-45d3f21a9da5.png)

新しいチャンネルのフォームで、
情報を入力します。
"アプリの種類"で、"ネイティブアプリ "をトグルします。

![Screenshot 2023-03-24 at 15.11.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/6e562954-0cde-56b7-1bb3-875383618b88.png)

リストに自分のチャンネルが表示されるので、クリックして開きます。

![Screenshot 2023-03-24 at 15.10.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/ef2a5dc7-46eb-630c-ce85-280993b1ce12.png)

「LINEログイン設定」にて、iOSアプリケーションのバンドルIDを入力します。

![Screenshot 2023-03-24 at 16.07.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/6e38b3cf-fe79-3d94-3c86-de6cd8948baf.png)

![Screenshot 2023-03-24 at 15.13.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/1e4f15aa-c15e-ad84-329e-d728abbe4587.png)

ここで、「チャンネル基本設定」タブにある `チャンネルID` を覚えておきましょう

![Screenshot 2023-03-24 at 15.12.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/e7975084-4447-649f-b367-32bf4ceae3ba.png)

また、チャンネルを公開とマークすることを確認してください。

## SDKを統合

`LineSDK` はSwift Packageを使用しても配布されています。

プロジェクトに追加するには、Xcodeの「File」メニューをタップし、「Add Packages」をクリックしてください。以下のURLを入力します：

`https://github.com/line/line-sdk-ios-swift.git`

![Screenshot 2023-03-24 at 15.20.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/9a27c4c8-9548-60db-a511-1a34e071d56e.png)

そして、「LineSDK」にチェックを入れ、メインとなるiOSターゲットを選択し、フレームワークを追加します。

![Screenshot 2023-03-24 at 15.20.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/0bab3195-1ba1-51a6-47a3-05d2646bb776.png)

## URLタイプの追加

ユーザーがLINEで認証されると、LINEはURLスキーマを使ってあなたのアプリを呼び出します。そのURLスキーマを定義する必要があります。
プロジェクトファイルを開き、iOSアプリのターゲットを選択し、`Info` タブで、以下のURLスキーマを追加します。

`line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)`:

![Screenshot 2023-03-24 at 15.23.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/d1ad7b34-4596-6b41-1f90-d15a9d72b66e.png)

## SDKの初期化

SwiftUIアプリのファイル（@main`があるファイル）内で、
SDKをインポートし、
アプリ起動時にLine SDKを初期化するためのAppDelegateアダプタを追加します。

```diff_swift
import SwiftUI
+import LineSDK

@main
struct LearnEnglishGPTApp: App {
    
+    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

+class AppDelegate: UIResponder, UIApplicationDelegate, UIWindowSceneDelegate {
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
+        LineSDK.LoginManager.shared.setup(channelID: "ooooooooo", universalLinkURL: nil)
+        return true
+    }    
+}

```

`channelID`には、チャンネルページのチャンネルIDです。

## 認証結果の処理

SwiftUIアプリの場合、
認証結果を処理するために`onOpenURL`を使用する必要があります。
SwiftUIアプリのファイルに、
ログインを処理するコードを追加します：

```diff_swift
import SwiftUI
import LineSDK

@main
struct LearnEnglishGPTApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
+                .onOpenURL { openedURL in
+                    if openedURL.absoluteString.contains("line3rdp") {
+                        _ = LineSDK.LoginManager.shared.application(UIApplication.shared, open: openedURL)
+                    }
                }
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate, UIWindowSceneDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        LineSDK.LoginManager.shared.setup(channelID: "ooooooooo", universalLinkURL: nil)
        return true
    }
    
}
```


## SwiftUI ビューにログインボタンを追加する

ログインの状態を監視するために、`observed`オブジェクトを使用することにします。
ここでの値は、ログインボタンを表示するビューに伝え返されます。
また、より多くの情報を含むカスタム構造体を使用することもできます。

```swift
class LineLoginStatus: NSObject, ObservableObject {
    @Published var isLoggingIn: Bool = false
    @Published var signInSuccessful_userID: String?
    @Published var errorMessage: String?
}
```

まず、SwiftUIでUIKit Lineログインボタンを表示するための互換ビューを作成する必要があります：

```swift
import SwiftUI
import LineSDK

struct LineLoginButtonCompatibleView: UIViewRepresentable {
    
    @ObservedObject var stateManager: LineLoginStatus
    
    func makeUIView(context: Context) -> UIView {
        let containerView = UIView()
        let loginButton = LoginButton()
        loginButton.delegate = context.coordinator
        loginButton.permissions = [.profile, .openID]
        containerView.addSubview(loginButton)
        // auto layout
        loginButton.translatesAutoresizingMaskIntoConstraints = false
        let centerX = loginButton.centerXAnchor.constraint(equalTo: containerView.centerXAnchor)
        let centerY = loginButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
        NSLayoutConstraint.activate([centerX, centerY])
        return containerView
    }
    
    func updateUIView(_ view: UIView, context: Context) { }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, LoginButtonDelegate {
        
        var parent: LineLoginButtonCompatibleView
        
        init(_ parentView: LineLoginButtonCompatibleView) {
            self.parent = parentView
        }
        
        func loginButton(_ button: LoginButton, didSucceedLogin loginResult: LoginResult) {
            print("Line login success! \(loginResult.userProfile?.userID) \(loginResult.userProfile?.displayName) \(loginResult.accessToken.value)")
            parent.stateManager.signInSuccessful_userID = loginResult.userProfile?.userID
            parent.stateManager.isLoggingIn = false
        }
        
        func loginButton(_ button: LoginButton, didFailLogin error: LineSDKError) {
            print("Line Login Error \(error.localizedDescription)")
            parent.stateManager.isLoggingIn = false
            parent.stateManager.errorMessage = error.localizedDescription
        }
        
        func loginButtonDidStartLogin(_ button: LoginButton) {
            print("Line login started")
            parent.stateManager.isLoggingIn = true
        }
    }
    
}
```

![Screenshot 2023-03-24 at 15.43.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/9c1002c6-fd2c-c9b0-3942-e0b710baf53a.png)

ログインボタンを表示するビューにログインの状態を報告するために、`LineLoginStatus`を使用しています。
その中で、
`@Published`変数を使用し、
これらの変数値の変更によってビューを更新します
（そして.onChangeビューモディファイアを呼び出します）。

##SwiftUIビューにLINEのログインボタンを追加する

さて、SwiftUIのビューにボタンを追加することができます：

```swift
struct SignInSheet: View {
    
    @ObservedObject var lineLoginStatus: LineLoginStatus = .init()
    
    @State private var loggedInUserID: String?
    @State private var errorMessage: String?
    @State private var isProcessingLogin: Bool = false
    
    var body: some View {
        
        Form {
            
            Section("Button") {
                LineLoginButton(stateManager: self.lineLoginStatus)
                    .onChange(of: self.lineLoginStatus.signInSuccessful_userID) { newValue in
                        if let newValue {
                            DispatchQueue.main.async {
                                self.loggedInUserID = newValue
                            }
                        }
                    }
                    .onChange(of: self.lineLoginStatus.errorMessage) { newValue in
                        if let newValue {
                            DispatchQueue.main.async {
                                self.errorMessage = newValue
                            }
                        }
                    }
                    .onChange(of: self.lineLoginStatus.isLoggingIn) { newValue in
                        DispatchQueue.main.async {
                            self.isProcessingLogin = newValue
                        }
                    }
                    .frame(height: 45)
                    .padding(.top, 5)
            }
            
            Section("Result") {
                if isProcessingLogin {
                    ProgressView()
                }
                if let loggedInUserID {
                    Label(loggedInUserID, systemImage: "checkmark.circle")
                }
                if let errorMessage {
                    Label(errorMessage, systemImage: "xmark")
                }
            }
            
        }
        
    }
}
```

onChange`ビューモディファイアを使って、ユーザーIDの変更（サインイン成功時）とエラーメッセージ（エラー時）を監視しています。

これで、このアプリを実行し、ユーザーがログインに成功したときにユーザーIDを確認することができます。

---

お読みいただきありがとうございました。

[🐘 マストドン @me@mszpro.com](https://sns.mszpro.com/@me)

[☺️ Twitter @MszPro](https://twitter.com/MszPro)

**☺️** サイト <https://MszPro.com>

<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">

![writing-quickly_emoji_400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/640938e9-c121-8152-b8c4-8ab63124afcc.png)

Written by MszPro\~