달력

5

« 2024/5 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

'SWIFT'에 해당되는 글 2

  1. 2021.08.12 Flutter에서 카카오톡 채널 추가하기 9
  2. 2020.10.08 Swift에서 WKWebView Scroll 문제 해결
2021. 8. 12. 20:53

Flutter에서 카카오톡 채널 추가하기 개발/Flutter2021. 8. 12. 20:53

반응형

Flutter를 한지 반년 이상 지났지만, 바쁘기도 했고 보통 pub.dev에서 갖다썼기 때문에

마땅히 포스팅할게 없어서 이제서야 관련 글을 작성합니다.

 

최근, 카카오톡 채널 추가하기 기능을 추가해달라는 요청을 받았는데 찾아보니 pub.dev에도 없고..

네이티브 소스를 좀 작성해야 가능한 것처럼 보여서 코틀린과 Swift로 작업을 진행했습니다.

카카오 개발 설정은 다른 블로그 글도 많기 때문에 생략했습니다.

설정하셔서 해시키도 등록해두시고 native app key를 얻으셔서 진행하시길 바랍니다.

 

그리고 제가 이해하기로는 카카오톡 친구를 강제적으로 추가해주는 API는 존재하지 않아보이고,

(가입시 카카오에서 제공하는 체크박스 제외)

카카오톡 친구를 보여주어 채널을 추가를 유도하는 정도의 기능만 구현할 수 있어보여서 그렇게 진행하였습니다.

 

(22. 9. 1) 1년이 지난 지금, 찾아보니 Kakao에서 Flutter를 지원해서 아래 링크를 따라하시면 간단히 할 수 있게 되었네요.

아마 kakao_flutter_sdk 버전 1.0.0이 공식 출시 되면서 22년 3월 경에 해당 링크와 기능을 지원하기 시작한거 같습니다.

낮은 확률로 다른 패키지와 충돌해서 kakao_flutter_sdk를 사용하실 수 없을 수도 있으니 

제가 작성한 소스도 남겨 놓도록 하겠습니다.

가능하시면 아래 링크를 통해 연결해보세요.

https://developers.kakao.com/docs/latest/ko/kakaotalk-channel/flutter

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

1. yaml을 작성한다.

flutter_kakao_login: 3.3.0
url_launcher: 6.0.3 # 카카오톡 설치 여부를 위해 추가

 

2. main.dart를 작성한다.

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_kakao_login/flutter_kakao_login.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '카카오톡 채널 추가 Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: '카카오톡 채널 추가 Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void goKakaoChannel() async {
    final installed = await canLaunch(Platform.isIOS ? "kakaokompassauth://authorize" : "kakaolink://");
    if (installed) {
      final FlutterKakaoLogin kakaoSignIn = new FlutterKakaoLogin();
      await kakaoSignIn.init("your_native_app_key");
      const MethodChannel _channel = const MethodChannel('myChannel');
      _channel.invokeMethod('addKakaoChannel', "your_channel_public_id");
    } else {
      //카카오톡이 설치되지 않았을때 처리
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            FloatingActionButton(
              onPressed: goKakaoChannel,
              tooltip: '카카오톡 채널 추가',
              child: Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}

 

3.  build.gradle(Project)에서 카카오 레파지토리 설정한다.

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
    }
}

 

4. build.gradle(Module)에 디펜던시를 설정한다.

dependencies {
    implementation "com.kakao.sdk:v2-talk:2.4.2" // 친구, 메시지(카카오톡)
}

 

5. MainActivity.kt를 작성한다.

package com.risha.blog

import androidx.annotation.NonNull

import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

import com.kakao.sdk.talk.*
import com.kakao.sdk.common.util.*

class MainActivity: FlutterActivity() {

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        val channel = MethodChannel(flutterEngine.getDartExecutor(), "myChannel")
        channel.setMethodCallHandler(handler)
    }


    private val handler: MethodChannel.MethodCallHandler = MethodChannel.MethodCallHandler({ methodCall, result ->
        if (methodCall.method.equals("addKakaoChannel")) {
            val channelPublicId = methodCall.arguments as String
            val url = TalkApiClient.instance.channelChatUrl(channelPublicId)

            // CustomTabs 로 열기
            KakaoCustomTabsClient.openWithDefault(context, url)
        } else {
            result.notImplemented()
        }
    })
}

 

6. Podfile을 작성한다. 

# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'

pod 'KakaoSDKTalk'  # 친구, 메시지(카카오톡)

작성 뒤, pod install합니다.

 

7. Info.plist를 작성한다.

    <key>LSApplicationQueriesSchemes</key>
    <array>
        <!-- common -->
        <string>kakao${your_native_app_key}</string>

        <!-- KakaoTalk login -->
        <string>kakaokompassauth</string>
        <string>storykompassauth</string>
    </array>

 

8. AppDelegate.swift를 작성한다.

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    var navigationController: UINavigationController!
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    linkNativeCode(controller: controller)
    GeneratedPluginRegistrant.register(with: self)
    
    self.navigationController = UINavigationController(rootViewController: controller)
    self.window.rootViewController = self.navigationController
    self.navigationController.setNavigationBarHidden(true, animated: false)
    self.window.makeKeyAndVisible()
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

    private func addKakaoChannel(call: FlutterMethodCall, result: @escaping FlutterResult) {
        let vc = UIStoryboard.init(name: "Main", bundle: .main)
                                .instantiateViewController(withIdentifier: "ViewController") as! ViewController
                        if let arguments = call.arguments as? String {
                            vc.arguments = arguments
                        }
        
                        vc.result = result
                        self.navigationController.pushViewController(vc, animated: true)
    }
}

extension AppDelegate {
    
    func linkNativeCode(controller: FlutterViewController) {
        setupMethodChannel(controller: controller)
    }
    
    private func setupMethodChannel(controller: FlutterViewController) {
        let commonChannel = FlutterMethodChannel(name: "myChannel",
                                                  binaryMessenger: controller.binaryMessenger)
        
        commonChannel.setMethodCallHandler({
          (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
          // Note: this method is invoked on the UI thread.
            
            //let viewController = ViewController()
            if call.method == "addKakaoChannel" {
                self.addKakaoChannel(call: call, result: result)
            }
        })
    }
}

 

9. ViewController.swift를 작성한다.

import UIKit
import SafariServices
import KakaoSDKTalk

class ViewController: UIViewController, SFSafariViewControllerDelegate {
    var result: FlutterResult!
    var arguments: String!
    var safariViewController : SFSafariViewController? // to keep instance
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.safariViewController = SFSafariViewController(url: TalkApi.shared.makeUrlForChannelChat(channelPublicId: arguments)!)

        guard (self.safariViewController != nil) else { return }

        self.safariViewController?.modalTransitionStyle = .crossDissolve
        self.safariViewController?.modalPresentationStyle = .overCurrentContext
        self.safariViewController?.delegate = self
        self.present(self.safariViewController!, animated: true) {
            print("Kakao Talk Channel chat 연결 페이지 실행 성공")
        }
    }
    
    func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
        //To access the  Specific tabBar ViewController
        self.navigationController?.popViewController(animated: true)
    }
}

 

10. Storyboard ID를 작성한다.

AppDelegate.swift의 instantiateViewController(withIdentifier: "ViewController")는

Main.storyboard에 ViewController를 추가하신 뒤, Storyboard ID를 withIdentifier 값으로 기입하시면 됩니다.

 

 

 

 

 

참고 출처 :

https://blog.usejournal.com/integrating-native-third-party-sdk-in-flutter-8aab03afa9da

https://developers.kakao.com/docs/latest/ko/kakaotalk-channel/ios

 

반응형
:
Posted by 리샤씨
2020. 10. 8. 13:20

Swift에서 WKWebView Scroll 문제 해결 개발/Swift2020. 10. 8. 13:20

반응형

최근 회사에서 운영 중인 iOS 어플의 웹뷰를 앱 스토어의 권고 사항에 따라 UIWebView에서 WKWebView로 변경하였다.

WKWebView로 바꾸어서 그런지 일부 최신 폰에서 (보고 받은 것은 iPhone X, iPhone XR)

fixed 처리된 footer가 있는 줄 모르는 듯이 Scroll시 맨 아래까지 가지 않고 일부 내용을 가리게 되어 사용에 불편이 생겼다.

처음엔 그저 WKWebview의 문제인줄 알고 scroll bounces를 false로 적용하려고 하거나 status bar 문제 인가싶어 그 부분도 알아봤지만

더 찾아보니 iPhone 11에서 부터 Safe Area Zone이란 것이 생겨서 문제가 생긴 듯하였다.

그래서 해결한 코드를 공유하도록 하겠다.

 

import UIKit
import WebKit

class ViewController: UIViewController  {
    @IBOutlet var webView: WKWebView!
    
    private func setupWebView() {
        webView = WKWebView(frame: self.view.bounds, configuration: WKWebViewConfiguration())
        view.addSubview(webView)
        
        if #available(iOS 11.0, *) {
            webView.scrollView.contentInsetAdjustmentBehavior = .never
        } else {
            // Fallback on earlier versions
            webView.translatesAutoresizingMaskIntoConstraints = false
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0":webView]))
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-20-[v0]|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0":webView]))
        }
    }
    
    override func viewSafeAreaInsetsDidChange() {
        if #available(iOS 11.0, *) {
            webView.translatesAutoresizingMaskIntoConstraints = false
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0":webView]))
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(self.view.safeAreaInsets.top))-[v0]-(\(self.view.safeAreaInsets.bottom))-|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0":webView]))
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setupWebView()
    }
}

위 코드처럼 iOS 11 버전 이상인지 아닌지에 따라 제약 조건을 분기하면 해결된다.

Safe Area Zone이 iOS 11 버전 이상부터 생김에 따라

safeAreaInsets 기능도 같이 iOS 11 버전 이상에서만 지원해주기 때문에 분기 코드는 필수이다.

만약, 하단은 가득 채우고 싶다면 25줄에서 -(\(self.view.safeAreaInsets.bottom))-를 삭제하면 된다.

 

 

참고 출처 : https://wit.nts-corp.com/2019/10/24/5731

stackoverflow.com/questions/45421548/ios-wkwebview-status-bar-padding

반응형
:
Posted by 리샤씨


반응형
반응형