iOS에서 WKWebView 붙이는 방법

|

프로젝트내에 loginPage.html이 있다는 가정하에 다음과 같이 하면 된다.

참고로 import WebKit 를 반드시 해주어야 한다.

//request 만들고
guard let htmlUrl = Bundle.main.url(forResource:"loginPage", withExtension:"html") else { return }
let request = URLRequest(url:htmlUrl)
webview.load(request)

// UI 구성
self.view.addSubview(webview)
webview.layer.borderWidth = 4.0
webview.layer.borderColor = UIColor.red.withAlphaComponent(0.3).cgColor
webview.addToSuper(top: 10, left: 10, bottom: 10, right: 10)

iOS에서 javaScript를 사용해 HTML를 변경하는 예제

전체 코드를 우선 보자.

작동 순서는 아래와 같다.

  1. 웹페이지 로딩을 한다
  2. 로딩이 끝나면 자바스크립트 파일내의 mobileHeader()를 실행한다
  3. mobileHeader()는 웹페이지내의 h1 태그를 보고 내용을 변경한다
  4. 변경된 웹페이지를 갱신해준다.
class JSFromiOSViewController: UIViewController {
    
    var webview:WKWebView!
    override func viewDidLoad() {
        super.viewDidLoad()

        initWebview_with_callJsFunc_modifyHTML()
        loadUrl()
 
    }
    
    func initWebview_with_callJsFunc_modifyHTML(){
     
        //contents 만듬.
        let contentController = WKUserContentController()
        let userScript = WKUserScript(source: "mobileHeader()",
                                      injectionTime: .atDocumentEnd,
                                      forMainFrameOnly: true )
        //mobileHeader()라는 javaScript 함수가 html로딩완료후 호출됨.
        
        contentController.addUserScript(userScript)
        
        //config 만듬
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        //put it all together
        //자바스크립트 함수를 실행시킬 준비를 미리하고, 이를 configuration에 넣고
        //이를 다시 userContentController에 저장해둔후
        //웹뷰를 만든다.
        webview = WKWebView(frame: CGRect.zero , configuration: config )
    }
    
    func loadUrl(){
        
        //request 만들고
        guard let htmlUrl = Bundle.main.url(forResource:"loginPage", withExtension:"html") else { return }
        let request = URLRequest(url:htmlUrl)
        webview.load(request)
        
        // UI 구성
        self.view.addSubview(webview)
        webview.layer.borderWidth = 4.0
        webview.layer.borderColor = UIColor.red.withAlphaComponent(0.3).cgColor
        webview.addToSuper(top: 10, left: 10, bottom: 10, right: 10)
    }
     
}

  • initWebview_with_callJsFunc_modifyHTML() 메소드:

우선 WKUserContentController를 이용하여 WebView를 로딩하기 전에 JavaScript 메소드 연동을 미리해주어야 한다. WKWebViewConfiguration 의 userContentController` 의 인자로 세팅해준다.

  • loadUrl() 메소드:

번들로부터 HTML파일의 url을 가져오고 나서 webview.load(request) 를 사용하여 웹뷰를 로딩하는 부분.

JavaScript를 설정하는 부분을 간단히 그려보면 다음과 같다.

ascii. 그림 들어갈 곳.

다음은 필요한 HTML, JS파일이다.

loginPage.html 는 다음과 같다.

<!--여기부터가 웹페이지 구성내용임.-->
<!DOCTYPE html>

<html>
    <head>
        
        <style type="text/css">
            body {
                padding-top: 40px;
            }
        </style>
        <title>WKWebView Demo</title>
        <meta charset="UTF-8">
            </head>
    <body style="background-color: gray;">
        <h1>JS to Webview Sample</h1>
        
        <form class="form-signin" role="form">
            <div class="form-content">
                
                <label for="email" class="form-email">Already an account?</label>
                <br />
                <input type="email" id="email" class="form-control" placeholder="Email" required autofocus />
                <br />
                <input type="password" id="password" class="form-control" placeholder="Password" required>
                    <br />
                    
                    <button class="btn btn-lg btn-primary btn-block" type="button" onClick="sendLoginAction()">
                        Submit
                    </button>
                    </div>
        </form>
        <script type="text/javascript" src="main.js"></script>
    </body>
</html>

main.js의 내용

function sendLoginAction() {
try {
    webkit.messageHandlers.loginAction.postMessage(
                                                   document.getElementById("email").value + " " + document.getElementById("password").value
                                                   );
} catch(err) {
    console.log('The native context does not exist yet');
}
}

function mobileHeader() {
    document.querySelector('h1').innerHTML = "iOS에서 호출된 증거(이 텍스트는 main.js에서 가져옴)";
}

레퍼런스: // ref: https://benoitpasquier.com/ios-webkit-swift-and-javascript/ // ref: https://github.com/popei69/WebkitSample/blob/master/WebkitSample/ViewController.swift

iOS에서 javaScript를 사용해 HTML를 변경하는 예제

전체 코드


import UIKit
import WebKit


// HTML상의 js를 invoke시켜 iOS로 응답을 받아냄.
// 예) 웹상에 입력했던 "로그인아이디", "암호"를 iOS로 전송해줌.


class JSToiOSViewController_simple: UIViewController{
    
    var webview:WKWebView!
    override func viewDidLoad() {
        super.viewDidLoad()
        initWebview_then_callFromJs()
        loadUrl()
    }
    
    func initWebview_then_callFromJs(){
        
        //contents컨트롤러 만듬.
        let contentController = WKUserContentController()
        contentController.add(self, name:"doneAction")
        
        //config 만듬
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        //위 인자사용한 초기화
        webview = WKWebView(frame: CGRect.zero , configuration: config )
    }
    
    func loadUrl(){
        
        //request 만들고
        guard let htmlUrl = Bundle.main.url(forResource:"buttonPress", withExtension:"html") else { return }
        let request = URLRequest(url:htmlUrl)
        webview.load(request)
        
        // UI 구성
        self.view.addSubview(webview)
        webview.layer.borderWidth = 4.0
        webview.layer.borderColor = UIColor.red.withAlphaComponent(0.3).cgColor
        webview.addToSuper(top: 10, left: 10, bottom: 10, right: 10)
    }
    
}
extension JSToiOSViewController_simple:   WKScriptMessageHandler{

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        
        if message.name == "doneAction" {
            print("JavaScript 에서 메시지가 왔어요 \(message.body)")
        }
    }
    
}

buttonPress.html

<html>
    <head>
        <title>WKWebView Demo</title>
        <meta charset="UTF-8">
    </head>
    
    <body style="background-color: gray;">
        <h1>js함수를 기다림....내용을 적고, 완료버튼을 누르세요</h1>
        <input type="email" id="email" class="form-control" placeholder="Email" required autofocus />
        <br />
        
        <button class="btn btn-lg btn-primary btn-block" type="button" onClick="sendDoneAction()">
            완료
        </button>
        
        <script type="text/javascript" src="main.js"></script>
    </body>
</html>

MAIN.JS

function sendDoneAction() {
    try {
        webkit.messageHandlers.doneAction.postMessage ( document.getElementById("email").value );
    } catch(err) {
        console.log('The native context does not exist yet');
    }
}

일단 App을 실행한 하면 다음과 같은 화면이 나온다.

webview_jsToiOS_WebScreen

먼저 HTML파일과 JavaScript 쪽을 보자.

HTML 페이지가 로딩되면 텍스트필드와 버튼이 있다. 여기에 내용을 “하하호호”라고 적고 완료버튼을 누른다.

버튼을 누르면 onClick에 연결된 sendDoneAction() 이 실행된다. 이는 main.js에 있는 메소드이다.

sendDoneAction()내부에는 webkit.messageHandlers.doneAction.postMessage가 있다. 그 코드를 자세히 보면 doneAction이 포함됨을 알 수 있다.

이 doneAction이라는 문자열을 가지고 iOS쪽과 통신을 한다고 보면 된다.

이제 iOS쪽을 보자.

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 델리게이트가 있다.

이곳에서 아래와 같은 코드를 통해 javaScript로 부터 전송된 메시지 내용을 받을 수 있다.

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    
    if message.name == "doneAction" {
        print("JavaScript 에서 메시지가 왔어요 \(message.body)")
    }
}

알아야 할 것이 있는데, 위 델리게이트가 정상적으로 동작하기 위해서 WKWebView 설정할 때 아래와 같이 doneAction가 포함된 부분을 명시 해주어야 한다.

//contents컨트롤러 만듬.
let contentController = WKUserContentController()
contentController.add(self, name:"doneAction")

전체 구조

전체 구조를 보면 다음 그림과 같다.

sendDoneAction()의 화살표를 따라가보자.

webview_jsToiOS

이상.

Date에 대해

|

Starting

2018-07-22 Date에 대해 Swift의 Date == objc의 NSDate임.

Date는 특정시간을 뜻함. user의 로케일, 캘린더, 타임존과의 연관성은 없다.

let now = Date()
// Nov 20, 2016, 6:55 PM

let later = Date(timeIntervalSinceNow: 300)
// Nov 20, 2016, 7:00 PM

 let evenLater = Date(timeInterval: 300, since: later)
// Nov 20, 2016, 7:05 PM

하루가 86,400 seconds라고 보장하지는 않으므로 주의가 필요함.

ZSH 설명들

|

ZSH 설명들

맥을 만지막 만지막 셸을 만지작 만지작 웹서핑을 하다보면 멋드러진 셸화면 스크린샷들을 볼 수 있다.

아니 나도 맥인데 저 사람들은 어떻게 저런 멋진 화면으로 커스터마이징했지? 궁금해 지기 시작했다. 멋지게 커스터마이징한 대부분의 맥 터미널 화면들은 zsh라는 커스텀터미널을 사용하는 것이었다. 그래서 바로 깔아서 써 보았는데, 여러 시행착오 끝에 설치부터 활용하는 몇가지 팁을 알게 되었다.

설치방법 링크

도움 되는 링크들은 다음과 같다. 소중한 정보 감사합니다!

놀부블로그 설명 https://nolboo.kim/blog/2015/08/21/oh-my-zsh/

zsh와 oh-my-zsh의 설치와 사용법 https://aweekj.github.io/zsh/

설치사용

여기도 유용하: http://heetop.blogspot.com/2017/10/oh-my-zsh_12.html

imageSample

경로설정, 테마 알아내기

참고 사이트: 현재 테마명 알아내기: https://github.com/robbyrussell/oh-my-zsh/issues/5725

나는 아나콘다를 사용하기 때문에 zsh를 다시설치하면 conda 명령어가 되지 않았다. zshrc 파일에서 다시 경로를 설정해야 한다.

  • zshrc 의 path 설정하기

터미널에서 vi ~/.zshrc 실행후에 아래 경로를 추가함.

export PATH="/Users/username/miniconda3/bin:$PATH"

export PATH=”/Users/muzna/miniconda3/bin:$PATH”

저장하고 나옴. 저장하고 종료하는 vi 명령어는 wq이다.

source ~/.zshrc

  • 현재 테마를 알아내는 방법
~/quotes_login$ echo $ZSH_THEME
random

위처럼 하면 zshrc 파일에 실제 기록된 파라미터 이름만 나온다. 그럼 다른 방법을 알아보자.

print $RANDOM_THEME 를 타이핑해보니 실제로 적용된 정확한 테마명이 나온다. 아래 예를 보아 현재 테마는 simple 인 것으로 표시된다.

~/quotes_login$ print $RANDOM_THEME
/Users/muze9/.oh-my-zsh/themes/simple.zsh-theme
  • 괜찮은 테마들

simple.zsh-theme ~/quotes_login/quotes_login ls jnrowe.zsh-theme Ξ quotes_login/quotes_login → ls

miniconda3 설치법

Miniconda3-latest-MacOSX-x86_64.sh 를 다운받고 미니콘다—터미널에서 다음을 실행한다.

bash Miniconda3-latest-MacOSX-x86_64.sh

모두 설치하면 아래처럼 나오는데 zsh가 설치된 환경이라면 또 다시 path설정을 해주어야 함.

You may wish to edit your .bashrc to prepend the Miniconda3 install location to PATH:

export PATH=/Users/muzna/miniconda3/bin:$PATH

기타 설정

  • visual studio code 를 쉘에서 실행하고 싶다면.
1. Launch VS Code.
2. Open the Command Palette (⇧⌘P) and type 'shell command' to find the Shell Command: Install 'code' command in PATH command.

Operation - BlockOperation에 대하여.

|

예제를 위해 먼저 시간측정하는 Util.swift 파일을 하나 만들고 저장하자.

//  Util.swift

var lastTime:TimeInterval?

func startTimer( handler:()->()){

  let now = Date()
  handler()
  let howLongTimePassed = Date().timeIntervalSince(now)
  print("소요시간: \(howLongTimePassed)")
  
}

예제 시작!!!

1. 여러 개의 operation을 BlockOperation에 추가할 수 있다.


var globalValue : Int = 0
func singleOp() {
  
  let addBlockOperation = BlockOperation {
    self.globalValue = 2 + 5
    sleep(1)
  }
  
  startTimer {
    addBlockOperation.start()
  }
  //소요시간: 1.00106596946716 와 비슷하게 표시될 것임.
  print("값 -- : \(self.globalValue)")
  

}

Operation은 synchronos하게 실행됨. 즉 동기로!!!. 비동기가 아니다. Operation 자체는 동기 작업(synchronous task)과 같다. 만약 원한다면 GCD 큐에 작업을 dispatch할 수도 있다.이렇게 하면 main thread를 벗어나서 백그라운드에서 원하는 작업할 수 있다.

그 다음 코드를 보자. BlockOperation를 먼저 만들고 addExecutionBlock를 사용하여 task들을 추가하는 것을 볼 수 있다.

func multipleOperations()
{
  let blockOp2 = BlockOperation()
  blockOp2.addExecutionBlock {  print("hello") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("my") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("name") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("is") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("kuku") ; sleep(1) }
  
  startTimer {
    blockOp2.start()
  }
  //소요시간: 2.00279700756073 로 나옴. 즉 concurrency가 발생했다.    
}

총 5초가 걸릴것 같지만 2초 조금 넘게 걸렸다. BlockOperation는 단순히 default global dispatch queue의 래퍼(wrapper)이다. 즉 5개의 블럭을 실행하고자 default queue의 스레드 3개를 사용한 것 처럼 보인다.

중요: 블럭은 각각의 블럭에 대해 independent 하다. A 블럭이 B 블럭의 결과를 조작할 수는 없음.

3. dispatch group 처럼 동작

func multipleOperations_withCompletionBlock()
{
  let blockOp2 = BlockOperation()
  blockOp2.completionBlock = {
    print("모두 수행완료")
  }
  blockOp2.addExecutionBlock {  print("hello") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("my") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("name") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("is") ; sleep(1) }
  blockOp2.addExecutionBlock {  print("kuku") ; sleep(1) }
  
  startTimer {
    blockOp2.start()
  }
  
}

4. Operation 서브 클래싱하는 법

A. 먼저 Operation 서브클래스를 만들자

class TiltShiftOperation: Operation {
  var inputImage: UIImage?
  var outputImage: UIImage?
  
  override func main() {
    outputImage = makeBlur(image: inputImage)
  }
 
 func makeBlur(image: UIImage?) -> UIImage? {
    guard let image = image else { return .none }
    sleep(1)
    //image를 blur하게 만드는 작업 생략.
    return image 
  }
}

B. 그다음 위위 Operation를 서브클래싱한 TiltShiftOperation를 사용한다. 예시는 다음과 같다.

let inputImage = UIImage(named: "dark_road_small.jpg")
let processor = TiltShiftOperation()
processor.inputImage = inputImage

startTimer {
  processor.start() 
  //소요시간: 1.11426794528961 , 근데 tableView등에서 쓰기에는 좀 느리다~~~
}

if let output = processor.outputImage {
  print(output)
}

Operation 클래스는 start()를 호출하면, 그때부터 작업이 수행된다. 단. TiltShiftOperation의 내부 동작은 생각하지 말자.

현재 ssh key를 알고 싶을때.

|

현재 ssh key를 알고 싶을때. 그리고 terminal 의 파일 내용을 clipboard로 복사하고 싶다면. cd ~./ssh로 들어가서 cat xxxx.rsa | pbcopy 하면 현재 내용을 보여준다.

아무것도 없네..보자.

eddiek@ek.local:/Users/eddiek/.ssh  $ ls -al
total 0
drwx------   2 eddiek  staff    64 Aug 22 14:44 .
drwxr-xr-x+ 72 eddiek  staff  2304 Aug 24 09:00 ..

eddiek@ek.local:/Users/eddiek/.ssh  $ open .

다른 맥에서 id_rsa파일 두개를 복사해서 현재 폴더에 복사한 후 다시 ls명령어를 타이핑해본다.

eddiek@ek.local:/Users/eddiek/.ssh  $ ls -al
total 16
drwx------   4 eddiek  staff   128 Aug 24 09:00 .
drwxr-xr-x+ 72 eddiek  staff  2304 Aug 24 09:04 ..
-rw-r--r--   1 eddiek  staff  3243 Jul  7 13:44 id_rsa
-rw-r--r--   1 eddiek  staff   732 Jul  7 13:44 id_rsa.pub
eddiek@ek.local:/Users/eddiek/.ssh  $ cat ./id_rsa.pub
ssh-rsa BBQAAB3NzaC1yc2EVVDsaaaAACAeeeYZofLk6i8D3IN7MG4Uf6iSEPRlXf2dxlKu3Yy0V7v4vINFp3ILSB+MCZK7C3vbr/mqDy69liU49+ZESQD0vOw1obKw81icqiaAAzqnNySEH4bd3Ocoq9EMp6eqa49599OYJ88hZqcGfH...

권한 관련 ssh에 문제가 생기면 chmod 400 ~/.ssh/id_rsa로 해서 해결했다.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/tudouya/.ssh/vm/vm_id_rsa.pub' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: /Users/tudouya/.ssh/vm/vm_id_rsa.pub
Permission denied (publickey,password).

사이트: https://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac