iOS 에서의 Javascript Interface Bridge는 단방향, JS -> Native로밖에 값을 전달하지 못한다.

따라서 js에 setValue() 등의 함수를 사용해야 한다..

그러나 window.webkit.messageHandler.foo.postMessage(args) 를 호출하고, 네이티브에서 evalJavascript로 setFooValue(bar) 함수를 호출해 밸류를 지정해준다 한들, 완성된 js코드를 보는 후임 개발자 입장에서 코드가 쉽게 눈에 들어오지 않을 것이고, 예쁘지도 않다. 또 리턴받아야 하는 함수가 여러 개인 경우 setValue() 함수를 여러번 생성해야 하며, Android JavascriptInterface Bridge와 다른 방식으로 사용해야 한다.

안드로이드만큼 편하지는 않겠지만, 그래도 기존 방식보다 편하면서 간결하고, 안드로이드와 따로 호출하지 않아도 될만한 코드를 고민해봤다.

 

메인 js에 객체 생성

var nativeObj = {
    coordinate:{
        getFromNative: function(val){
            if(nativeObj.nativeType.val == "Android"){
                this.latitude = window.android.get_latitude();
                this.longitude = window.android.get_longitude();
            }else if(nativeObj.nativeType.val == "iOS"){
                window.webkit.messageHandler.getCoordinate.postMessage("");
            }else{
                alert('앱에서만 이용이 가능한 기능입니다.');
            }
        },
        latitude:0,
        longitude:0
    },
    userToken: {
        getFromNative: function(){

        },
        val:""
    },
    userState: false,
    nativeType: {
        get: function(){
            var agent = navigator.userAgent.toLowerCase();
            is_android = agent.indexOf('app_android') > 0;
            is_ios = agent.indexOf('app_ios') > 0;
            if(is_android)
                this.val = "Android";
            else if(is_ios)
                this.val = "iOS";
            else
                this.val = "Web";
        },
        val: "Web"
    }
};

 

AndroidBridge.java

    @JavascriptInterface
    public double get_latitude(){
        GpsTracker gpsTracker = new GpsTracker(mainActivity);
        return gpsTracker.getLatitude();
    }

    @JavascriptInterface
    public double get_longitude(){
        GpsTracker gpsTracker = new GpsTracker(mainActivity);
        return gpsTracker.getLongitude();
    }

 

ViewController.m

- (void)userContentController:(WKUserContentController*)userContentController
  didReceiveScriptMessage:(WKScriptMessage*)message {
    if([message.name isEqualToString:@"getCoordinate"]) {
      [self getCurrentLocation];
      if(latitude != nil && longitude != nil){
          NSString *javaScript = [NSString stringWithFormat:@"window.nativeObj.coordinate.latitude = %@; window.nativeObj.coordinate.longitude = %@;", latitude, longitude];
          [self.wkWebView evaluateJavaScript:javaScript completionHandler:nil];
      }
   }
}

 

 

함수 호출시

window.nativeObj.coordinate.getFromNative();

 

 

물론 객체를 사용하지 않거나, 객체를 사용하더라도 콜백 등을 구현해 이보다 간결하게 작성할 수 있겠지만.. 
네이티브 함수 호출이 그다지 많지 않을 것 같아서 이렇게만 해야겠다.

'Mobile > iOS' 카테고리의 다른 글

[Objective C] Javascript와 WKWebView 통신 시 리턴값 받기  (0) 2021.05.28

카톡로그인 인텐트를 아래와 같이 처리해줬다.

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
  if (url.startsWith("intent:")) {
    try {
        Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
        Intent existPackage = getPackageManager().getLaunchIntentForPackage(intent.getPackage());
        if (existPackage!=null) {
        	startActivity(intent);
        } else {
        	Intent marketIntent = new Intent(Intent.ACTION_VIEW);
        	marketIntent.setData(Uri.parse("market://details?id=" + intent.getPackage()));
        }
      return true;
    } catch (Exception e) {
    	e.printStackTrace();
    }
  } 
  else {
  	view.loadUrl(url);
  }
  return true;
}

실사용에서는 참 편하지만 테스트할 때 만큼은 정말 불편하다.

특히 여러대의 테스트기기로 로그인을 해야하는데..
현재 개발중인 서비스는 카톡로그인 외의 로그인방법이 존재하지 않는다.

 

방법이 없을까 고민하다가 Extra를 자세히 보니 브라우저에서 로드되는 URL이 있어서 아래와 같이 처리해줬다.

  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    Log.d("MainIntent URL", url);
    if (url.startsWith("intent:")) {
    	try {
    		Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
    		Intent existPackage = getPackageManager().getLaunchIntentForPackage(intent.getPackage());
    		if (existPackage!=null) {
    			startActivity(intent);
    		} else {
    			Intent marketIntent = new Intent(Intent.ACTION_VIEW);
    			marketIntent.setData(Uri.parse("market://details?id=" + intent.getPackage()));
    		}
    		return true;
    	} catch (Exception e) {
        	try{
            	Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);

                if(intent.getAction().contains("kakao")){
                	view.loadUrl(intent.getStringExtra("browser_fallback_url"));
                }
                else {
                    Intent marketIntent = new Intent(Intent.ACTION_VIEW);
                    marketIntent.setData(Uri.parse("market://details?id=" + intent.getPackage()));
                }
            }
            catch (Exception ex){
                ex.printStackTrace();
            }
      	}
    } 
    else {
      view.loadUrl(url);
    }
    return true;
  }

 

이제 카톡이 깔려있지 않아도 카카오 계정으로 사용이 가능하다.

 

MainActivity.java

    private long backKeyPressedTime = 0;
    private Toast toast;
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
        	// 웹뷰 History상 이전 페이지가 있을 경우
            if(mWebView.canGoBack()){
                mWebView.goBack(); // 뒤로가기
                return true;
            }
            // 없을 경우 앱 종료 전 Toast로 물어보기
            else{
            	// 토스트메세지 출력
                if (System.currentTimeMillis() > backKeyPressedTime + 2000) {
                    backKeyPressedTime = System.currentTimeMillis();
                    toast = Toast.makeText(this, "뒤로가기 버튼을 한번 더 누르시면 종료됩니다.", Toast.LENGTH_SHORT);
                    toast.show();
                    return true;
                }
                // 토스트메세지가 있는 상태에서 뒤로가기를 한번 더 누르면 앱 종료
                else if (System.currentTimeMillis() <= backKeyPressedTime + 2000) {
                    finish();
                    toast.cancel();
                }
            }
        }
        return super.onKeyDown(keyCode, event);
    }

 

최종 결과물 

Activity에서 처리하는 경우

activity_main.xml

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <WebView
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:id="@+id/mWebview"/>
    </LinearLayout>

MainActivity.java

    WebView mWebView;
    WebSettings mWebSettings;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.mWebview); // 웹뷰 선언
        mWebSettings = mWebView.getSettings();

        mWebView.setWebChromeClient(new WebChromeClient()); // 크롬클라이언트 사용
        mWebView.setWebViewClient(new WebViewClientClass()); // 웹클라이언트 설정 (아래)
        mWebSettings.setJavaScriptEnabled(true); // 자바스크립트 허용
        mWebSettings.setSupportMultipleWindows(false); // 여러 창 또는 탭 열리는 것 비허용
        mWebSettings.setLoadWithOverviewMode(true); // 페이지 내에서만 이동하게끔
        mWebSettings.setUseWideViewPort(true); // 페이지를 웹뷰 width에 맞춤
        mWebSettings.setSupportZoom(false); // 확대 비활성화
        mWebSettings.setBuiltInZoomControls(false); // 확대 비활성화
        mWebSettings.setCacheMode(mWebSettings.LOAD_NO_CACHE); // 캐시 사용안함 (매번 새로 로딩)
        mWebSettings.setDomStorageEnabled(true); // 로컬스토리지 사용 허용

        mWebView.loadUrl("접속할 URL");
    }

    // 웹클라이언트 세부 설정
    private class WebViewClientClass extends WebViewClient {
        // SSL 인증서 무시
        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }

        // 페이지 내에서만 url 이동하게끔 만듬
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    }

AndroidManifest에서 처리하는 경우

AndroidManifest.xml

    <application
        ...
        android:usesCleartextTraffic="true">
        ...

이전 포스팅 : 안드로이드 프래그먼트 활용하기

 

안드로이드 프래그먼트 활용하기

MainActivity.java public class MainActivity extends AppCompatActivity { private FragmentManager fragmentManager; private Fragment_Main fragment_main; private Fragment_MyPage fragment_my_page; privat..

jyspw.tistory.com

 

사전 작업
1. res 폴더 우클릭 -> New -> Android Resources Directory -> menu 폴더 생성
2. res/drawable -> New -> Vector Asset -> 사용할 Clipart 이미지 생성

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private FragmentManager fragmentManager;
    private Fragment_Main fragment_main;
    private Fragment_MyPage fragment_my_page;
    private FragmentTransaction fragmentTransaction;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragmentManager = getSupportFragmentManager();

        fragment_main = new Fragment_Main();
        fragment_my_page = new Fragment_MyPage();

        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
 
 	// 이벤트 리스너 등록
        BottomNavigationView bottomNavigationView = findViewById(R.id.navBar);
        bottomNavigationView.setOnNavigationItemSelectedListener(new MenuClickEventListener()); 
    }
    class MenuClickEventListener implements BottomNavigationView.OnNavigationItemSelectedListener{
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            fragmentTransaction = fragmentManager.beginTransaction();

            switch(item.getItemId())
            {
                case R.id.btn_main:
                    fragmentTransaction.replace(R.id.frameLayout, fragment_main).commitNowAllowingStateLoss();
                    break;
                case R.id.btn_my_page:
                    fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
                    break;
            }
            return true;
        }
    }

    /* 
    // 이전 포스팅 코드
    public void MenuClickHandler(View v){
        fragmentManager = getSupportFragmentManager();
        fragmentTransaction = fragmentManager.beginTransaction();

        switch (v.getId()){
            case R.id.btn_main:
                fragmentTransaction.replace(R.id.frameLayout, fragment_main).commitNowAllowingStateLoss();
                break;
            case R.id.btn_my_page:
                fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
                break;
        }
    }*/
}

 

res/menu/nav.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/btn_main"
        android:icon="@drawable/ic_baseline_home_24"
        android:title="Main" />
    <item
        android:id="@+id/btn_my_page"
        android:icon="@android:drawable/ic_menu_manage"
        android:title="My Page" />
</menu>

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/navBar"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/navBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="parent"
        app:menu="@menu/nav" />

</androidx.constraintlayout.widget.ConstraintLayout>

최종 결과물

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private FragmentManager fragmentManager;
    private Fragment_Main fragment_main;
    private Fragment_MyPage fragment_my_page;
    private FragmentTransaction fragmentTransaction;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragmentManager = getSupportFragmentManager();

        fragment_main = new Fragment_Main();
        fragment_my_page = new Fragment_MyPage();

        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
    }

    public void MenuClickHandler(View v){
        fragmentManager = getSupportFragmentManager();
        fragmentTransaction = fragmentManager.beginTransaction();

        switch (v.getId()){
            case R.id.btn_main:
                fragmentTransaction.replace(R.id.frameLayout, fragment_main).commitNowAllowingStateLoss();
                break;
            case R.id.btn_my_page:
                fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
                break;
        }
    }
}

 

activity_main.xml

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/linearLayout"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent">

        <Button
            android:id="@+id/btn_main"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="MenuClickHandler"
            android:layout_weight="1"
            android:text="Main" />

        <Button
            android:id="@+id/btn_my_page"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="MenuClickHandler"
            android:layout_weight="1"
            android:text="MyPage" />
    </LinearLayout>

 

각 Fragment Java

public class Fragment_이름 extends Fragment {
    @Nullable
    @Override
    public View onCreateView(
        @NonNull LayoutInflater inflater,
        @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState
    ){
        return inflater.inflate(R.layout.fragment_이름, container, false);
    }
}

 

테스트용 Fragment Layout xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="This" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="is" />

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="a" />

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Main Page" />
    </LinearLayout>
</LinearLayout>

 

 

결과물

 

+ Recent posts