아주 간단하게 모바일 웹페이지 상단에 앱스토어 배너 넣기

요즘 스마트폰이 활성화 되면서 모바일 서비스를 제공시 iOS, Android 등의 스마트폰용 앱 주력으로 사용하는 경우가 많습니다.
하지만 앱 설치 유도, 접근성 등으로 모바일 웹페이지도 함께 제공을 할 수 밖에 없죠.
물론 주력 서비스는 앱이기 때문에 다양한 방법으로 모바일 웹페이지에서 앱으로의 이동을 유도합니다.

iOS에서는 아주 간편하게 해결 할 수 있습니다.

<meta name=”apple-itunes-app” content=”app-id=앱아이디”>

Meta Tag 하나를 추가하여

이런 형태의 배너를 모바일 웹페이지에 추가할 수 있습니다.

Meta Tag 내에 넣어야 하는 앱아이디는 앱스토어 링크에서 알 수 있습니다.

https://itunes.apple.com/us/app/apple-store/id375380948?mt=8

~store/id 뒤에 있는 것을 사용하면 됩니다. 375380948

 

앱을 설치하기 전에는 위 배너가 앱스토어로 이동되지만 앱을 설치한 후에는 앱으로 열어 줍니다.
앱을 연 주소를 가지고 앱에서 대응을 하려면

AppDelegate.swift 에 application(_:open:options:) 를 추가한 후 URL에 맞게 대응할 수 있습니다.

https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application

 

Advertisements

‘Guideline 2.5.1 – Performance – Software Requirements’ ‘Your app uses or references the following non-public APIs:’ 리젝에 대응하는 방법.

애플에서 허용하지 않는 API를 사용하는 경우에 앱이 리젝 당합니다.
당연히 사용하지 말라고 한 API를 사용한 코드를 제거하고 다시 심사요청을 하면 되겠죠.

그런데 iOS 앱 개발자들은 일반적인 앱을 만들 경우 비허용/비공개 API를 사용할 일이 잘 없습니다.
Xcode에서 검색을 해도 안 나오겠죠.
가져다 쓴 라이브러리나 타 서비스 SDK에서 사용한 경우일 것입니다.
간단하게 외부 바이너리 라이브러리에서 비허용 API를 사용하고 있는 지 확인하는 방법을 알려 드리겠습니다.

프로젝트 폴더 혹은 외부 바이너리 라이브러리가 있는 폴더에서
터미널 명령으로

grep -R ‘애플에서 알려 준 non-public API’ *

예를 들자면 광고 대행사 SDK를 많이 가져다 쓰면 발생할 수 있는 리젝 사유인 LSApplicationWorkspace

grep -R ‘LSApplicationWorkspace’ *

어느 광고 대행사 SDK때문에 리젝된 것인지 알 수 있습니다.

 

 

WKWebView 사용 시 필수적으로 넣어줘야 하는Delegate 넷.

WKWebView를 사용한다면 아래의 넷은 반드시 넣어 주어야 한다. 복사->붙여 넣기 후 개발을 시작하면 된다.

우선, WKUIDelegate 셋.

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping () -> Void) {
    let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { (action) in
        completionHandler()
    }))

    self.present(alertController, animated: true, completion: nil)
}

func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (Bool) -> Void) {
    let alertController = UIAlertController(title: "", message: message, preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { (action) in
        completionHandler(true)
    }))
    alertController.addAction(UIAlertAction(title: "취소", style: .default, handler: { (action) in
        completionHandler(false)
    }))

    self.present(alertController, animated: true, completion: nil)
}

func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (String?) -> Void) {
    let alertController = UIAlertController(title: "", message: prompt, preferredStyle: .alert)
    alertController.addTextField { (textField) in
        textField.text = defaultText
    }
    alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { (action) in
        if let text = alertController.textFields?.first?.text {
            completionHandler(text)
        } else {
            completionHandler(defaultText)
        }
    }))

    alertController.addAction(UIAlertAction(title: "취소", style: .default, handler: { (action) in
        completionHandler(nil)
    }))

    self.present(alertController, animated: true, completion: nil)
}

이건 iOS 9.0 이후 지원이긴 한데… WKNavigationDelegate 하나.

public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
    // 중복적으로 리로드가 일어나지 않도록 처리 필요.
    webView.reload()
}

앱이 백그라운드 상태에서 포그라운드로 돌아 올때 간혹 WKWebView가 하얀 화면으로 되어 있는 경우가 있는데, 보통 자바스크립트 등으로 체크해서 대응을 하고 있지만 iOS 9이상에선 지원하고 있다.

남아도는 우분투 리눅스 서버가 있어서 토렌트를 설치했다.

토렌트를 거의 쓰지 않지만 아주 가끔 이용하는 경우가 있다.
그런데 보통 내가 토렌트를 이용하는 경우엔 매우 희귀한 자료라서 시드가 없거나 1인 경우가 많다.
며칠을 컴퓨터 켜 놓는 거 싫어하기 때문에 그냥 포기하는 경우도 있는데, 그래서 찾은 방법.

‘남아도는 우분투 리눅스 서버에 토렌트를 설치하자.’

개인적으로 필요해서 준비하던 서비스가 있어 도메인, 서버 등 모두 준비가 되었는데 텔레그램 봇 기능이 너무 막강해서 별도로 서비스를 구축할 필요가 없어졌다.

결국 서버와 도메인은 놀게 되었고, https://ip.devg.net (IP, Request Header 등 확인 용도) 이거 하나 돌리니 매달 내는 서버 비용만 아까워 죽을판이었는데 드디어 쓸모가 있게 생겼다.

설치는 간단하게.

$ sudo apt-get install transmission-daemon

끝.

아… 실행은.

$ sudo service transmission-daemon start

끝.

하지만 여기서 끝이면 리눅스가 아니지! 내 입맛에 맞게 쓰기 위해서 설정이 필요하다.
서비스가 돌아 가는 중에는 설정파일을 수정할 수 없으므로 우선 서비스 부터 종료 한다.

$ sudo service transmission-daemon stop

그리고 설정 파일 편집.

$ sudo vim /etc/transmission-daemon/settings.json

총 71여라인으로 상당히 많은 항목들이 있는데 간단히 알아두어야 항목들만 나열하자면.

  • “rpc-username”: “<로그인아이디>”
  • rpc-password”: “<로그인패스워드>” // 설정 파일을 저장하고 서비스를 실행하면 패스워드를 암호화 시켜 버리므로 설정 파일을 열어 패스워드가 노출되는 일은 걱정 하지 않아도 된다.
  • “rpc-whitelist-enabled”: false // 특정 아이피에서만 연결되도록 하는 기능이다 보안을 위해 필요하지만 편의상 끄도록 하겠다. 켜기 위해선 당연히 false를 true로 수정.
  • “ratio-limit-enabled”: true // 다운로드 대비 업로드 비율을 조절할 수 있다.
  • “ratio-limit”: 0.5 // 1은 토렌트로 다운받는 파일이 100MB 라면 100MB 를 업로드 한 후 Seeding이 멈춘다. 0.5는 50% 기본값은 2로 되어 있는데 기본값을 유지하는 선함을 추천한다.
  • download-queue-size: 2 // 동시에 다운로드하는 수를 제한 할 수 있다.
  • preallocation”: 1 // 다운로드 파일을 미리 생성하는 방식을 정한다. 1은 파일 용량만큼 미리 파일을 만들어 두고 다운로드 하면서 채우는 방식. 0은 다운로드 받으면서 파일 용량을 늘린다.
  • “download-dir”: “<다운로드경로>” // 가장 중요한 다운로드 경로. 기본값은 /var/lib/transmission-daemon/downloads

여러 이유로 download-dir을 변경할 수 있는데 변경 전 미리 폴더를 만들어서 권한을 주어야 한다.

$ cd /home/<계정명> #폴더를 만들 경로로 이동한다.
$ mkdir downloads #폴더를 생성한다.
$ sudo chgrp transmission-daemon downloads #서비스가 제어 가능하도록 권한 부여.

이제 서비스를 시작하고 웹으로 접속하여 토렌트를 이용하여 보자.

$ sudo service transmission-daemon start

http://리눅스서버의호스트네임:9091/

접속하면 http 인증을 요구하는데 rpc-username/rpc-password 설정한 정보를 넣어 준다. 9091은 기본 포트로서 settings.json에서 변경할 수 있다.

rpc.png

안드로이드는 앱으로 더욱 편하게 관리할 수 있다. (심지어 다운로드가 완료 되면 푸쉬로 알려 준다.)

‘Play 스토어’에서 ‘Transmission Remote’로 검색하면 여러 앱들이 나오는데 아래의 앱을 추천한다.

아쉽게도 iOS에서는 Remote 앱 조차 약관을 들어 앱스토어에 허용하지 않고 있다. 탈옥을 하지 않으면 Torrent 관련 앱은 쓸 수가 없다.
웹에서도 이미지 외 파일 업로드를 지원하지 않는 관계로 iOS에서는 마그넷 주소를 집어 넣는 방법 밖에 없다.

토렌트로 다 받은 파일을 안드로이드 폰으로 다운로드 받기 위해서 FTP나 SFTP를 이용할텐데 웬만한 유명 탐색기는 다 지원하지만 별도로 설치를 하지 않았다면 ‘디자인’이 제일 예쁜 아래 앱을 추천.


iOS에서는 아래의 앱을 추천. Lite라는 이름의 무료 버전도 있다. 

보통 토렌트를 이용하는 사람들은 동영상 불법 다운로드를 이용하기 위해서 일텐데, 서버에서 동영상은 다운받으려면 또 시간이 오래 걸려 불편하다. 그래서 FTP를 이용해서 스트리밍을 하는 방법을 추천한다.
‘ES 파일 탐색기’가 이 부분에선 가장 적절하다.

Linux 서버에 FTP 서버가 설치 되어 있지 않은 경우엔 SFTP를 이용하면 되는데 iOS에서는 nPlayer 라는 막강한 앱이 있지만 안드로이드폰에서는 SFTP를 이용한 스트리밍을 제대로 지원해주는 앱이 없다.

SFTP는 보안 때문에 속도도 느리고 안드로이드폰에서 스트리밍을 제대로 즐기기 위해서는 FTP 서버를 설치하는 게 좋기 때문에 다른 포스팅을 통해서 FTP 서버 설치와 설정을 적어 보겠다.

앱을 다시 시작하면 WKWebView 쿠키가 모두 사라져요.

WKWebView에서 생성된 쿠키가 앱을 재시작할때 모두 사라지는 문제는 여러 해결 방법이 있지만, 그 중 가장 단순한 건 역시 모든 통신 종료 시점에 쿠키를 저장했다가 앱을 시작하면 쿠키를 수동으로 생성 시켜 주는 방법일 것입니다.

이전에도 Javascript를 애용했으므로 이번에도 Javascript를 이용하도록 하겠습니다.

예제 상에서 파일을 저장하고 불러 오는 것은 Devg.framework를 이용하도록 하겠습니다.
(https://bitbucket.org/ZenR/devg.git)

우선 모든 웹 통신이 종료될 시 가지고 있던 쿠키를 평문으로 저장하도록 하겠습니다. 보안에 좀 더 신경을 쓴다면 한번 암호화를 거치는 것도 좋겠죠.

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation) {
    webView.evaluateJavaScript("document.cookie") { (object, error) in // 자바스크립트로 모든 쿠키를 가져와서
         if let string: String = object as? String { // 스트링으로 변환 후 저장.
             Devg.createFile("COOKIE", contents: string) // 파일 저장하는 부분이므로 대체 하세요.
         }
    }
}

 

 

이제 저장한 쿠키 정보를 불러와서 웹뷰에서 생성하는 작업을 해주어야 합니다.
적절한 도메인에 쿠키를 생성해 주기 위해 첫 웹뷰 로딩은 그냥 진행 시키도록 할게요.
결국 Javascript 기반이기 때문에 쿠키 생성도 didFinishNavigation 에서 진행해 주어야 하는데 쿠키 파일 저장과 생성하는 것이 한 곳에서 이루어 지므로 구분 하기 위해 isFirstLoad라는 멤버 변수를 만들어 주겠습니다.

var isFirstLoad: Bool = true

이제 isFirstLoad 변수를 참조하여 didFinishNavigation에 쿠키 생성하는 코드를 추가 합니다.

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation) {
    if self.isFirstLoad { // 앱 처음 실행인지 판단.
        self.isFirstLoad = false // 쿠키 생성을 다시 하지 않도록 처리.
        if let string: String = Devg.getContents("COOKIE") { // 저장된 쿠키 파일을 불러 옴.
            // 쿠키를 생성하는 Javascript
            let cookies: [String] = string.componentsSeparatedByString(" ")
            var javascript: String = ""
            for cookie: String in cookies {
                javascript = javascript + "document.cookie='\(cookie)';"
            }
            webView.evaluateJavaScript(javascript, completionHandler: { (object, error) in // 쿠키를 생성하는 Javascript를 실행.
                webView.reload() // 쿠기 생성 후 쿠키 정보가 반영된 웹페이지를 불러 올 수 있도록 새고 고침을 해줍니다.
            })
        }
    }
    else {
        webView.evaluateJavaScript("document.cookie") { (object, error) in // 자바스크립트로 모든 쿠키를 가져와서
             if let string: String = object as? String { // 스트링으로 변환 후 저장.
                 Devg.createFile("COOKIE", contents: string) // 파일 저장하는 부분이므로 대체 하세요.
             }
        }
    }
}

https://ip.devg.net 은 사용자 아이피, 헤더 정보 등을 확인할 수 있는 웹페이지인데, 쿠키 정보도 볼 수 있으므로 간단히 테스트 후 실제 URL을 적용하시면 됩니다.

WKWebView 서로 간에 Cookie 공유하는 방법.

iOS 8 이상 부터 사용할 수 있는 WKWebView는 UIWebView와 다르게 서로 다른 인스턴스 간에 쿠키가 공유되지 않는다.

쿠키를 서로 공유하게 하기 위해선 서로 같은 WKProcessPool을 사용하게 하면 된다.

self.processPool = WKProcessPool()

let configuration1: WKWebViewConfiguration = WKWebViewConfiguration()
configuration1.processPool = self.processPool

let webView1: WKWebView = WKWebView(frame: CGRectZero, configuration: configuration)

let configuration2: WKWebViewConfiguration = WKWebViewConfiguration()
configuration1.processPool = self.processPool

let webView2: WKWebView = WKWebView(frame: CGRectZero, configuration: configuration)