달력

42024  이전 다음

  • 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

'모바일 개발'에 해당되는 글 4건

  1. 2017.12.14 WKWebVIew 에서 쿠키 2
  2. 2017.12.14 javascript로 웹에서 App 열기
  3. 2017.10.25 초기화에 대해서..
  4. 2017.09.19 WebView 새창 띄울 때 Issue 사항...

일단, 나는 UIWebView 를 써보지 않았다. WKWebVIew만 써봤다. Android에 WebView랑 비슷한 듯.


하이브리드 앱에 홈페이지 로그인 할 때 자동 로그인 체크하고, 로그인을 하게 되면 앱이 종료되면 쿠키 삭제ㅋ 됨ㅋ 자동 로그인 안됌ㅋ 뭐지... 하고 이런저런 테스트를 해봤는데, WKWebView에는 php에 setcookie가 안 먹히드라. 결국 자동로그인 쿠키가 생성이 안됨ㅋ

안되면 되게 하라... 스크립트로 처리를 함.

AppDelegate에서


func applicationWillResignActive(_ application: UIApplication) {
    web.evaluateJavaScript("document.cookie", completionHandler: {(object, error) in
        if let string: String = object as? String {
            UserDefaults.standard.set(string, forKey: "cookie")
        }
    })
}

이렇게 앱이 foreground에서 벗어나게 되면 쿠키를 수시로 저장시키자. 아, 근데 php setcookie로 생성된 쿠키는 저기에서 확인이 안된다.ㅋ

 일단 WKWebView에 WKUserContentController() 를 통해서 자바스크립트 인터페이스(안드로이드에서는 이렇게 부른다 swift에서는 콘텐츠컨트롤러라고하는건가...?)를 등록하고, 로그인 처리하는 페이지에서 스크립트로 실행 한 후에 UserDefault에다가 자동로그인 체크를 했다는 정보를 넘겨주어서 앱에서 자동으로 쿠키를 생성시켜서 처리를 했다.



let contentController = WKUserContentController();
let config = WKWebViewConfiguration();
contentController.add(self, name: "javascriptInterfaceName"); // name 에다가 원하는 자바스크립트인터페이스 이름을 입력하면 되시겠다.
config.userContentController = contentController;
let webview = WKWebView(frame: uiController.view.frame, configuration: configurate!)

그리고 웹페이지에서 어떻게 실행을 시키는 고하면

webkit.messageHandlers.javascriptInterfaceName.postMessage( { key1 : value1, key2 : value2, key3 : [ value3_1, value3_2 ] } );

위 소스를 실행시키면 된다.


위 소스를 실행시키게 되면, 

@available(iOS 8.0, *)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// message.name 이녀석이 javascriptInterfaceName 이랑 매칭이 된다. 요녀석으로 용도별로 처리하면된다.
// 보안 문제가 발생 할 경우가 있으니까 꼭 if message.name != "javascriptInterfaceName" {return} 이런 식으로 지정한  스크립트가 아니라면 막길 권한다.

    guard let mbody = message.body as? Dictionary<String,Any> else {return} // json 형식으로 파라미터 값을 넘겼으니까 Dictionary로 변환 가능하다.
    let value1 = mbody["key1"] as? Any
    let value2 = mbody["key2"] as? Any
    let value3 = mbody["key3"] as? Array<Any> // 마지막은 리스트형식이니까,
}

WKWebView 내의 이런 소스에서 실행이 된다. 참고로 iOS8 부터 사용 가능하다.

이렇게 아무튼 저기서 자동로그인 체크를 했다는 것을 앱으로 전달해서 UserDefault에 다가 저장했다가 앱 실행시마다 자동로그인 쿠키값을 생성해주면 되겠다. 어떻게?? 아래처럼 참고하자.


아래 setCookie에 해당하는 메소드는 구글링을 통해서 찾았으나, 출처를 따로 안적어놨네..........죄송합니다..

webview.evaluateJavaScript("document.setCookie = function (name, value, expiredays) {var cookie = name + \"=\" + escape(value) + \"; 
path=/;\"; if (typeof expiredays != 'undefined') {var todayDate = new Date(); todayDate.setDate(todayDate.getDate() + expiredays); cookie += \"expires=\" +todayDate.toGMTString() + \";\"; } 
document.cookie = cookie;}",completionHandler: nil)

WKWebView에다가 저렇게 하고

webview.evaluateJavaScript("document.setCookie('\(key)','\(value)', '\(expiredays)')", completionHandler: {
    (o,e) in web.reload()
})

이렇게 key(쿠키 명), value(쿠키 값), expiredays(저장 기간) 값을 넣어서 쿠키를 생성시키고, 페이지 새로고침을 해줘야 제대로 인식을 한다.

마지막으로 저기 document에 체이닝 되어진 setCookie 메소드를 제거해주자.

webview.evaluateJavaScript("delete document.setCookie", completionHandler: nil)


이렇게 하면 깔끔하게 처리가 된다.

'모바일 개발 > iOS-Swift' 카테고리의 다른 글

초기화에 대해서..  (0) 2017.10.25
Posted by
|

 ios 도 그렇고 android도 그렇고  url 형식으로 앱에 접근이 가능하다. 앱이 설치 되어 있다면 앱을 실행 시키고, 앱이 설치되어 있지 않다면 앱 설치 페이지로 전환 시켜주는게 가능하다. 그리고 앱에 특정 데이터를 전달 해서 원하는 기능을 제공 할 수 있다.


일단 기본 적인 android 와 ios 둘 다 메커니즘은 동일하다. user-action을 통한 이벤트여야지 동작을 한다는 것과, setTimeout을 이용해서 마켓으로 이동을 시켜야 한다는 것. 

소스로 확인 해보자.


    var androidPackage = "my.app.package";
    var appleAppId = ""; // id1401934913 형식으로 itunes url에 나오는 앱아이디.
    var androidMarketUrl = "market//detail?id="+androidPackage;
    var iosMarketUrl = "http://itunes.apple.com/kr/app/" + appleAppID;
    function goStore(){
        var startTime = +new Date();
        setTimeout(
            function() {
                var now = +new Date();
                if ( now - startTime < 1000){
                    var marketUrl = "";
                    if( isAndroid ){
                        marketUrl = androidMarketUrl;
                    }else if( isiOS ){
                        marketUrl = iosMarketUrl;
                    }
                    location.href = marketUrl;
            }
        , 500)
        if ( isAndroid ){
            openAndroid();
        }else if ( isiOS ){
            openiOS();
        }
    }

    function openAndroid(){
        var userAgent = navigator.userAgent.toLowerCase();
        if ( userAgent.match(/chrome/) ){
            location.href = "intent://hostName?param1=someValue1¶m2=someValue2/"
                                        +"#Intent;scheme=schemeName;action=android.intent.action.VIEW;"
                                        +"category=android.Intent.category.BROWSABLE;package="+androidPackage;
        }else{
            var iframe = document.createElement( 'iframe' );
            iframe.style.visivility = 'hidden';
            iframe.src = 'schemeName://hostName?param1=someValue1¶m2=someValue2';
            document.body.appendChild(iframe);
            document.body.removeChild(iframe);
        }
    }

    function openiOS(){
        location.href = "schemeName://hostName?param1=someValue1¶m2=someValue2";
    }

안드로이드는 버전에 따리 분기 처리해줘야 하는데 요즘은 schemeName 을 intent로 고정해서 사용한다고 한다. 이전 버전에서는 iframe을 이용하는게 제일 깔끔하다고 어디서 봐서 iframe으로 사용 했다. 

중요한 부분은 3 함수 모두 사용자 액션 기반으로 발동이 되어야 한다는 것이다. onload나 그런 것이 아닌 dom을 클릭해서 실행 하던지 comfirm() (confirm만으로는 안된다. dom을 클릭해야만 발동되더라..) 을 통해 처리 해야 한다는 것이다.

안드로이드에서는 schemeName 과 hostName은 Manifest파일에 실행 될 activity 안에 아래 내용을 붙여 주면 된다.


<activity android:name="name" android:label="label" android:theme="theme">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"> </action>
        <category android:name="android.intent.category.BROWSABLE"></category>
        <category ........=""> </category>
        <data android:host="hostName" android:scheme="schemeName"></data>
    </intent-filter>
</activity>

이렇게 넣어주면된다. 보면 action과 category도 맞춰줘야하는 건 쉽게 알 실듯 여러분들은 똑똑하니까

iOS 의 경우는 캡쳐로 때움.ㅋ

identifier랑 schemeName이랑 동일하게 하면 된다. 다르게 해도 되는 지는 모르겠다.ㅋ  iOS에서는 hostName은 따로 설정하지 않아도 된다.


자 이제 실행시킨 앱에서 파라미터를 받아서 어떻게 처리 하는지를 보자.

안드로이드 먼저

아까 실행될 acitivity의 onCreate에서 작업하면 된다.

    if( getIntent().getScheme() != null && getIntent.getScheme().equals( "schemeName" ) ){
        String someValue1 = getIntent().getData().getQueryParameter( "param1" );
        String someValue2 = getIntent().getData().getQUeryParameter( "param2" );
        //do....
    }

아래는 iOS 11 기준으로. iOS 는 버전별로 어디어디서 실행되는지는 모르겠다. iOS8 까지는 아래대로 하면 문제없을듯 하다. AppDelegate에 아래 함수를 수정해주자. 없으면 추가해주고.

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        guard let query = url.query else {return false}
        let querys = query.components(separatedBy: "&")
        var keyValue = Dictionary<string,string>()      
        for q in querys {
            guard let point = q.index(of: "=") else {continue}
            let keyEnd = q.index(before: point)
            let valueStart = q.index(after: point)
            
            let key = String(q[q.startIndex...keyEnd])
            var value = String(q[valueStart..< q.endIndex])  
            keyValue[key] = value
        }
        //dosomethings..
        return true
    }

나의 경우는 http url 을 파라미터로 넘겨서 앱에 있는 webview를 해당 url 로 넘겨주는 것을 구현했는데, iOS에서 병신같은 일이 일어났다.

파라미터로 넘긴 url 이  http://www.someDomain.co.kr/folder1/page.php/ 였다고 가정을 하면 끝에 /를 붙였다고 페이지 로딩 직후 바로 이전 url로 넘겨버리는 엄청난....병신같은...................ㅡㅡ 첨엔 내가 소스를 잘못 짠줄 알았는데 알고보니 / 때문이였다. 안드로이드는 상관없이 잘된다... WKWebView 버그인지는 잘 모르겠다. 근데, ㅋ 웃긴게 openurl로 넘겨온게 아니면 슬러쉬 있든 없든 잘된다는 거다.ㅋ


    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        guard let query = url.query else {return false}
        let querys = query.components(separatedBy: "&")
        var keyValue = Dictionary<string,string>()
        
        for q in querys {
            guard let point = q.index(of: "=") else {continue}
            let keyEnd = q.index(before: point)
            let valueStart = q.index(after: point)
            
            let key = String(q[q.startIndex...keyEnd])
            var value = String(q[valueStart..< q.endIndex])  
            
            //url 끝에 슬러쉬 붙었다고 페이지 이동안됌. 이뭐병.... 끝에 슬러쉬를 없애주자...
            while value.hasSuffix("/"){
                let suffixIndex = value.index(value.endIndex, offsetBy: -1)
                value = String(value[value.startIndex..< suffixIndex])
            }
            keyValue[key] = value
        }
        //dosomethings..
        return true
    }

이러니 잘되네............. 결국 저 슬러쉬 문제를 정리하기 위해서 겸사겸사 쓴 포스트.

Posted by
|

class 초기화만 해당된다.

class SomeClass : NSObject{
    let someValue1 : TempObject;
    let someValue2 : TempObject2?
    var someValue3 : Int;
    init( _ someParam : TempObject){
        someValue1 = someParam;
        someValue2 = TempObject2();
        super.init();
        someValue3 = 5;
    }
}


일단 지정 초기화, 편의 초기화 설명 보다 먼저,
let 으로 선언된 변수는 super.init() 이전에 초기화가 이루어 져야한다.
그리고 나중에 설명 하겠지만 let 변수는 꼭 (designed)정의 초기화에서 초기화가 되어야 한다.
일단 옵셔널로 정의된 건 nil 로 초기화가 가능한데, let 변수이니, nil로 선언되면 변경이 안되니 nil로 초기화 할 거면 let 이 아니라 var 로 선언 하는게 좋을듯 한다.

편의 초기화 접두어는 convenience 를 사용 하면 된다. 뭣 하는 녀석인고 하면

class SomeClass {
    init(v1 : Int, v2 : Int, v3 Int){
        //someCode
    }
    convenience init(v1 : Int){
        self.init(v1 : v1, v2 : 5, v3 : 3)
    }
}

요런거에 사용 되는 녀석이다. 딸랑 요것만 있음 뭔지 모르지.. 아래에 있는 걸 다시 보자.

class SomeClass {
    init(v1 : Int, v2 : Int? = nil, v3 : Int? = nil){ //지정 초기화
        //someCode
    }
    convenience init(v1 : Int){ // 편의 초기화
        self.init(v1 : v1, v2 : nil, v3 : nil)
    }
}

//초기화
let someClass : SomeClass = SomeClass(1);

v1 파라미터만 넘기고 싶을때 사용하면 된다.
내부적으로 v2, v3 에는 nil 이 지정 초기화로 넘어가게 된다.

여기서 조금 생소한 부분이 나온다.
편의 초기화는 내부에 꼭 다른 초기화 내용이 있어야 한다. 위의 self.init(v1 : v1, v2 : nil, v3 : nil) 과 같이 말이다.
여기서 편의 초기화에서 편의 초기화를 호출 해도 된다.

class SomeClass {
    init(v1 : Int, v2 : Int? = nil, v3 : Int? = nil){ // 마지막 호출
        //someCode
    }
    convenience init(v1 : Int){ //최초 호출
        self.init(v1 : v1, v2 : nil)
    }
    convenience init(v1 : Int , v2 : Int){ //두번쨰 호출
        self.init(v1 : v1, v2 : v2, v3 : nil);
    }
}
// 초기화
let someClass : SomeClass = SomeClass(v1 : 3);

여기서 상속 받을 경우는 어떻게 되는가를 궁금해 하는 사람이 있을 것 같아 제일 위에 있는 소스를 참고해서 보자.

class SomeClass : NSObject{
    let someValue1 : TempObject;
    let someValue2 : TempObject2?
    var someValue3 : Int;

    override init(_ someParam : TempObject , someParam2 : TempObject2){
        someValue1 = someParam;
        someValue2 = someParam2;
    }

    convenience init(_ someParam : TempObject, someParam2 : TempObject2){
        self.init(someParam, someParam);
        someValue3 = 5;
    }

    convenience init( _ someParam : TempObject){
        let someParam2 = TempObject2();
        self.init(someParam, someParam2);
    }
}
//초기화
let someParam : TempObject = TempObject();
let someClass : SomeClass = SomeClass(someParam);

위에서 설명했듯이 let 변수는 정의 초기화 함수 내에서 초기화가 이루어져야 한다.
위에 정의 초기화를 super class의 init을 오버라이드 했다.
오버라이드 하지 않고 쓰기 위해서는 아래 처럼 간단하게 사용 하면된다.

class SomeClass : NSObject{
    let someValue1 : TempObject;
    let someValue2 : TempObject2?
    var someValue3 : Int;

    init(_ someParam : TempObject, someParam2 : TempObject2){
        someValue1 = someParam;
        someValue2 = someParam2;
        super.init();
        someValue3 = 5;
    }

    convenience init( _ someParam : TempObject){
        let someParam2 = TempObject2();
        self.init(someParam, someParam2);
    }
}
//초기화
let someParam : TempObject = TempObject();
let someClass : SomeClass = SomeClass(someParam);

마찬가지로 정의 초기화에서 let을 초기화 시켜주어야 한다.

첫 초기화로 호출 되어 초기화가 이루어진다. 두번째 호출이라고 주석 단 부분에 또한 편의 초기화이다.
ㅇㅇ 쉽게 convenience -> convenience 가 된다. 그리고 그 끝은 designed(지정) 초기화가 있어야만 한다.
구글링 해보면 몇 가지 법칙이라고 정리 되어 있던데, 난 그런 법칙은 모르겠구, 내 수준으로 정리를 하면
1. let 변수는 super.init() 호출 전에 초기화가 되어야 하며, 정의(Designed) 초기화내부에서만 초기화가 가능하다.
2. 편의->편의->지정 같이 초기화의 마무리는 항상 지정 초기화가 되어야 한다.

Java 스타일의 초기화랑 비교를 하면, Java 의 경우 초기화 함수를 여럿 만들어 주면 된다. 그냥 입맛에 맞게 만들어서 사용하면된다. 그리고, super 클래스 초기화를 부르고 싶으면 쓰고, 안쓰고 싶으면 안 쓰면 된다.
super class의 초기화도 오버라이드 하든 말든 마음대로 내키는 대로 하면 된다. 초기화 선언이 없으면 고냥 자동으로 만들어 준다.
Swift는 조금 색다르게 표현했다. 편의(convenience) 초기화는 말 그대로 편의를 위해서 만들어지는 정식적인 초기화는 아니고, 정의(Designed)초기화를 통해서만 모든 초기화가 이루어진다고 보면 된다.

'모바일 개발 > iOS-Swift' 카테고리의 다른 글

WKWebVIew 에서 쿠키  (2) 2017.12.14
Posted by
|

안드로이드 개발 하다가 빡치는 상황 발생..

webview에서 처리하는 것중에, 새창 띄우는 것을 처리해줘야 하는데, 이게, 새창으로 띄우는게 여간 귀찮은게 아니다. 새창만 띄우는 것이면, 좀 덜 귀찮은데, 이게, 새로 뜨는 webview의 user-agent를 기존 부모뻘이 되는 webview와 동일하게 변경하니까, 서버에서 이걸 못 받는건지... php5 버전대 버그인지,,, 안드로이드 버그인지............

간략하게 정리하면 

<a target="_blank"> 또는 javascript:window.open(); 를 처리하려면, WebChromeClient를 설정해서, onCreateWindow를 재정의를 해줘야 한다.

근데, onCreateWindow를 통해서 만들어지는 새창 WebView의 user-agent를 서버에서 인식을 못한다!

구글링구글링을 통해서 찾은 소스 하나(https://stackoverflow.com/questions/9654529/handle-url-from-oncreatewindow-web-view)

        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
            Message href = view.getHandler().obtainMessage();
            view.requestFocusNodeHref(href);

            String url = href.getData().getString("url");
            handleUrl(url); // handle the url here

            return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
        }


이얏호! 찾았따, 히히,

근데 스크립트로 새창을 열때는 안된다. <a href=""> 만 가능하다.

아오........................

그래서 결국 꼼수로


        @Override
        public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg) {
            WebView temp = new WebView(context);
            temp.setWebViewClient(new WebViewClient(){
                @Override
                public void onPageStarted(WebView view, String url, Bitmap favicon) {
                    //여기서 새로운 커스텀된 webview 생성후, 처리를 해주면 된다.useragent를 처리해주면 된다.
                    // webview 생성후 부모레이아웃에 addView로 생성된 뷰를 추가해주자,
                    // 생성하면서 기본 설정도 여기서 다 처리해주면 된다.
                    //super.onPageStarted(view, url, favicon); 이건 주석처리 해놓고,
                }
            });
            WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
            transport.setWebView(temp);
            resultMsg.sendToTarget();

            return true;
        }


얏호, 완료.

파라미터로 넘어오는 resultMsg에 분명 링크되는 정보들이 들어가 있는거는 분명한데,, 이걸 빼먹질 못하게 되어 있다. 빼먹을려면 WebViewProvider부터 해서 새로 작업을 해야하는 것 같다.. 제조사마다 WebViewProvider를 재정의해서 컴파일하는 듯 하다. 안드로이드 레퍼런스에 참고할만한 소스가 하나도 없다. 크롬소스를 토대로 만들라고했는데, 너무 배보다 큰 배꼽이래서 포기...

resultMsg의 오브젝트로 포함되는 WebViewTransport에 웹뷰를 설정해주고, resultMsg를 이미 정의되어져있는, 그러나 우리는 알지 못하는, 타겟으로 보낸다. 그럼 자동으로 WebViewTransPort에 설정된 웹뷰에 이런저런 짓거리를 해서 페이지 전환이된다. 이런저런 짓거리를 우리는 알지 못한다!! 아마 webviewProvider에 있을 것같은 느낌이 든다. 

이런저런 짓거리 중에 user-agent를 기본 시스템 user-agent로 되돌리는 것도 아닌 것 같은데, 왜 그런지 모르겠다. 로그를 찍으면 설정한 user-agent값이 제대로 찍히긴 하는데, 서버측에서는 제대로 인식을 못하는건지 아오 생각 할 수록 짜증....

처음에는 WebChromeClient의 onProgressChange(WebView view, int newProgress) 를 통해서 프로그레스가 30이상 되었을때, reload하면서 useragent를 다시 설정해주는 방식으로 하다가, 이건 좀 아닌 거 같잖아? 페이지가 살짝 뜨다가 지 혼자서 페이지를 새로고침을 하다니. 이건 좀 아니잖아.....  깔끔하지 못해! 

그래서 위의 방식대로, 사용자는 모르게 처리하게끔 처리 했다. 맘에 들진 않는데, 현실과 타협해버렸다.

Posted by
|