Unity에서 Swift 코드로 iOS native 구현하기(feat. HealthKit)

Danna 다나·2022년 7월 6일
5

iOS와 Unity3D

목록 보기
1/2
post-thumbnail

Unity가 다양한 플랫폼과의 호환을 거의 완벽할 정도로 구사하고 있긴 하지만, 아무래도 코드 내에 native 코드가 필요할 때가 아예 없는 건 아닙니다.
특정 OS에서 프레임워크 API로 내려주고 있는 코드를 쓰고 싶을 때, 즉 iOS의 경우에는 -Kit 형태로 이름이 붙은 HomeKit, HealthKit, MapKit, ARKit 등등의 프레임워크를 쓰는 경우가 좋은 예시가 될 것 같습니다.

Unity로 개발하던 중에 iOS의 HealthKit과 연동해 기기 사용자의 건강 정보를 읽고 업데이트 하고 싶었는데, 한글 자료는 물론 외국 자료도 마땅치 않더라구요. 그래서 작성하는 글입니다.

기본적으로 Unity 프로젝트에 Swift 코드를 임베드 하는 방법을 설명하고, HealthKit 연동 방법까지 공유합니다.


0️⃣ Unity와 iOS 네이티브 프레임워크 연동

희망편 - 주류 프레임워크

네이티브 alert 팝업, 갤러리/카메라 권한 사용을 위한 UIImagePickerController와 그 delegate 등은 워낙 Unity에서도 많이 사용되는 네이티브 프레임워크라 에셋 스토어에 무료 또는 아주 싼 가격에 플러그인이 공유 되고 있습니다.
이런 플러그인들은 비교적 최근까지 잘 관리가 되어 있고, 사용자 층이 두터워 커뮤니티 내에서 질문과 응답이 활발하게 오가기 때문에 디버깅도 간편합니다.

간단히 다운 받아서 시키는대로 설정한 후 시키는대로 코드를 쓰면 어려운 단계 없이 바로 동작합니다.

unity와 iOS 네이티브 프레임워크 연동 희망편입니다.


절망편 - 비주류 프레임워크

하지만 내가 쓰려 하는 프레임워크가 이렇게 자주 쓰이는 프레임워크가 아니라면?
제목에도 있는 HealthKit 역시 swift로 개발하는 iOS 네이티브 내에서도, 물론 Unity에서 iOS 앱 최적화를 할 때도 비주류에 속합니다.
사실 비주류라는 용어도 부끄러울 만큼 google에 그 어떤 자료도 나오지 않습니다.

사진에 캡쳐되어 있는 게 의미있는 검색 결과의 전부입니다.

그나마 BEHealthKit이라는 플러그인이 하나 나오긴 하는데, 1 seat 당 40달러라는 무서운 가격을 가지고 있고 공식적으로 제공하는 docs가 가격에 비해 친절하지 않았습니다.

결국 5만원을 지불하고도 stackoverflow와 기타 개발 커뮤니티를 전전하며 디버깅 해야 할 상황이 올 것 같았습니다.

결정적으로 BEHealthKit을 사용하다 stackoverflow에 질문한 이렇게 긴 글

답변이 딱 하나 달렸고, 그 마저도 2줄짜리에, 질문자의 댓글이 Yes, But...으로 시작하는 절망적인 케이스였습니다.

그렇다고 Unity에서 HealthKit 연동을 시도한 흔적(그 흔한 가이드 블로그글, 스택오버플로우 질문, 유니티 포럼 질문)이 많은 편도 아니었습니다. 다 합쳐서 겨우 다섯 손가락을 채울 정도였습니다.

이게 비주류의 슬픔인가?

결국 접근 방법을 바꾸기로 했습니다.

5만원을 주고도 오랫동안 헤매야 할 미래가 눈앞에 훤히 보였고, 그렇게 고생해봤자 HealthKit 연동 이외에는 쓸 구석이 없는 플러그인이라면 과감히 포기하기로 했습니다. 기왕 고생할 거 제대로 고생해서 HealthKit 뿐만 아니라 다른 Swift 코드도 쓰고 싶을 때 언제든지 써서 임베드 할 수 있는 확장성 있는 기반을 다져놓자 다짐했습니다.



1️⃣ 유니티 프로젝트에 Swift 코드 임베드 하기

Swift로 iOS 네이티브 앱 개발을 할 때 HealthKit을 써본 적이 있기 때문에 Swift 코드로는 HealthKit 연동이 매우 간단하다는 걸 알고 있었습니다.

그럼 그냥 Unity 프로젝트에서 Swift가 해석되게 하면 되는 거 아닌가? 라는 바보같고도 단순한 질문에서 시작되었습니다.
말처럼 간단하지 않았거든요 ㅎ


+) 왜 유니티에서 Swift 코드를 쓰지 못할까?

이 질문은 왜 unity에서 네이티브 최적화를 할 때는 objective-C를 쓸까?라는 질문의 연장선에 있습니다.

iOS 개발을 조금이라도 찍먹해본 사람은 다 알고 있듯 Objective-C는 이제 레거시 취급을 받고 있고, 예전에 개발되어 그나마 남아있는 Objective-C 코드들도 다 Swift로 리팩토링 하는 추세입니다.

그런데도 왜 유니티와 iOS 키워드로 구글에 검색하면 다 Objective-C 코드만 나오는지 궁금했습니다. 1-2년 내에 쓰인 비교적 최근 글들임에도 불구하고요.

(아래는 꼭 iOS 플러그인을 만드는 데 Objective-C를 써도 괜찮을 경우 참고할 만한 괜찮은 자료들입니다)
[YouTube] Unity3d iOS Plugins - How To Create An iOS Plugin With Unity3d ?
[YouTube] Unity3d iOS Plugins - How To Call Native iOS Alerts From Unity ?
[Medium] Integrating native iOS code into Unity

유니티(C#)은 스위프트와 바로 소통하지 못합니다.
그래서 우리는 Objective-C를 브릿지로 이용해야 합니다.

C#은 Objective-C와 소통할 수 있고, Swift 역시 Objective-C로 소통할 수 있으니 C#과 Swift 사이에 Objective-C라는 통역관을 하나 두는 것과 같습니다.

따라서 Unity에서는 Swift를 아예 쓰지 못한다라기보다는 Objective-C를 거치지 않고는 쓰지 못한다라는 표현이 더 정확합니다.

이 점을 고려하며 Swift 함수를 Unity에서 실행시키는 방법에는 몇 가지가 있습니다.
그 중 가장 간편하게 사용할 수 있었던 방법을 몇 가지 소개하겠습니다.


1. Swift Package -> Framework -> Unity Plugin

유니티 공식문서와 더불어 어느 한 유튜브 영상에서 설명하고 있는 방법입니다.

1) Xcode에서 Package 생성

Xcode에서 상단바 File > New > Package를 눌러 새로운 패키지를 생성합니다.

원하는 위치에 원하는 이름으로 패키지를 생성합니다.


2) 보일러 플레이트 정리

처음 Package가 만들어지면 크게 Source 폴더, Tests 폴더가 생길 것입니다.
하지만, Unity에서 빌드할 때는 패키지를 프레임워크로 추출해서 사용할 것이기 때문에 test 코드는 아예 실행되지 않습니다.
따라서 Test 코드 보일러 플레이트를 삭제해주도록 합시다. (사진에서 드래그 된 부분을 모두 삭제해주면 됩니다)

마찬가지로, Source 폴더를 보면 패키지와 같은 이름의 파일이 하나 생성되어 있는데, 이 보일러 플레이트 코드도 지워줍니다. (드래그 된 부분을 지워주면 됩니다)

이렇게 두 파일을 각각 class, struct가 정의된 상태로 만들면 본격적으로 코드 쓸 준비가 끝납니다.


3) 메서드 정의 파일 구현

JS로 리액트 개발을 할 때 index.js 파일과 비슷한 역할을 한다고 생각하면 좋습니다.
해당 패키지 내에 어떤 메서드가 있는지 정의하고 라우팅 하는 역할을 합니다.

원하는 이름으로 파일을 하나 생성해줍니다.

그리고 메서드를 하나 만들어 볼게요.
이 메서드는 유니티 프로젝트의 C# 코드에서 바로 호출할 메서드이므로, 이름에 Swift, NativeiOS 등을 포함해 이 메서드는 유니티 프로젝트 밖에 있음을 암시하는 이름이면 좋습니다.

그리고 별다른 로직 없이 바로 Swift Package Source 파일 내에 정의된 메서드를 호출해 줍니다.
실제 로직은 NativeiOSCode, 즉 패키지 이름으로 생성된 파일에서 구현합니다.

위 코드를 보면 익숙하지 않은 부분이 한 줄 있을 텐데요,
@_cdecl()은 C 베이스 코드에서 Swift 함수를 호출할 때 사용하는 비공식 attribute입니다.
공식화 하자는 논의는 꽤 있었으나, 플랫폼 호환성이나 안정성 면에서 아직은 공식화가 되지 않고 있습니다. bridging-header를 사용해 C 베이스 언어들과 소통하는 방법이 꾸준히 추천되고 있는 듯합니다.

어쨌든, @_cdecl("메서드 이름") 형태로 메서드 위에 적어주면 C 베이스 언어에서 Swift 코드와 소통할 수 있게 됩니다.
그래서 위 코드 이미지에서는 @_cdecl("NativeiOSCode_runNativeCode")로 적어주었습니다.

하지만 위에서도 잠깐 언급했듯, 컴파일 단계에서 안정성을 보장하지는 않습니다. (라고 하지만 스위프트 포럼을 보니 심각한 문제가 일어난 적은 없어 보입니다)


4) 실제 로직 메서드 구현

메서드 정의 파일에서 NativeiOSCode_runNativeCode 메서드를 만들고 NativeiOSCode.runNativeCode()로 어떤 메서드를 호출해주었는데요, 여기까지 하면 에러가 난 상태일 겁니다. 바로 해당 메서드가 구현이 되지 않은 상태이기 때문입니다.

다시 NativeiOSCode 파일(패키지 이름으로 생성된 파일)로 가 runNativeCode()라는 함수를 구현해줍니다.

간단히 콘솔에 프린트만 하는 코드를 작성했습니다.

물론, 매개변수가 있는 경우도 정상적으로 동작합니다.


5) xcodeproj 파일 생성

이 상태로 Finder에서 폴더에 들어가보면 xcodeproj 파일이 없는 걸 볼 수 있습니다.
간단한 명령어로 하나 만들어 줄 수 있습니다.

터미널에서 우리가 만든 Swift Package가 있는 폴더로 가 아래 명령어를 입력합니다.
swift package generate-xcodeproj --skip-extra-files

명령어가 성공적으로 실행되면 해당 폴더에 project 파일이 생성됩니다.


6) 패키지 빌드

xcodeproj 파일을 생성하고 나면 패키지를 빌드해 프레임워크 파일을 추출할 수 있습니다.
xcodebuild -project [패키지이름].xcodeproj -scheme [패키지이름]-Package -configuration Release -sdk iphoneos CONFIGURATION_BUILD_DIR=.

하나 하나 뜯어보면
-project [패키지이름].xcodeproj: [패키지이름] 프로젝트 파일에 대해
-scheme [패키지이름]-Package: 그 중 [패키지이름] 스키마를 (한 프로젝트 파일에 여러 스키마가 존재할 수 있습니다)
-configuration Release: 릴리즈 모드로
-sdk iphoneos: iPhoneOS(iOS)에서 사용할 sdk를
CONFIGURATION_BUILD_DIR=.: 현재 폴더로 빌드한다.

[패키지이름] 프로젝트 파일 중 [패키지이름] 스키마를 릴리즈 모드로 iPhoneOS(iOS)에서 사용할 sdk를 현재 폴더에 빌드한다.
라는 뜻의 명령어가 됩니다.

명령어가 성공적으로 실행되면, framework 파일과 dSYM 파일이 생성됩니다.

이 중 framework 파일이 우리가 유니티 프로젝트에서 플러그인으로 사용할 파일이 됩니다.


7) Unity로 프레임워크 import

Unity 프로젝트의 Assets 폴더 안에 Plugins > iOS 폴더를 만들고, 그 안에 framework 파일을 import 해줍니다.
간단히 드래그 앤 드롭 해주면 됩니다.

그 후 해당 framework 파일의 inspector로 가서 Platform settings > Add to Embedded Binaries 체크박스를 체크해주세요.
그 후 Apply를 눌러 적용시켜줍니다.


8) Unity C# Script에서 Swift 메서드 호출

원하는 Script에서 Swift 메서드를 호출해줍시다.

우선 외부 메서드를 우리가 script에서 쓸 수 있게 import 해주는 과정이 필요합니다.

using System.Runtime.InteropServices;

public class Showcase : MonoBehaviour 
{
	#if UNITY_IOS
    	[DllImport("__Internal")]
        private static extern void NativeiOSCode_runNativeCode();
    #endif
    
    void Start()
    {
    }
    
    // (하략)
}

코드를 조금 뜯어보자면, DllImport()는 외부 플랫폼의 플러그인을 동적으로 불러올 때 사용합니다. 괄호 안에 플러그인 이름을 적어주면 됩니다.
하지만 우리 코드는 DllImport("__Internal")로 쓰여있는 걸 볼 수 있는데요, 여기서 __internal은 정적으로 연결된 네이티브 코드를 쓸 때 사용합니다.
(더 자세한 설명은 유니티 공식문서에서 확인할 수 있습니다)

Note that when using Javascript you will need to use the following syntax, where DLLName is the name of the plug-in you have written, or “__Internal” if you are writing statically linked native code:

@DllImport (DLLName)
static private function FooPluginFunction () : float {};

그리고 함수 선언부의 extern 키워드는 해당 메서드가 유니티 프로젝트의 바깥에 있을 때, 즉 외부 플러그인을 불러올 때 사용합니다.

iOS 네이티브 플러그인은 실제 디바이스 배포 중에만 호출할 수 있기 때문에 이 코드는 특정 플랫폼에서만 실행될 수 있도록 래핑해주는 단계가 필요합니다. 그래서 우리는 코드를 #if UNITY_IOS로 감싸주었습니다.

그 후에는 코드를 바로 호출하기보다 내부에서 처리로직을 한 번 더 두어 안전한 호출을 해봅시다.

public class Showcase : MonoBehaviour 
{
    void Start()
    {
    	runNativeCode();
    }
    
    private void runNativeCode()
    {
    	#if UNITY_IOS
        	NativeiOSCode_runNativeCode();
        #else
        	Debug.Log("No iOS Device Found");
        #endif
    }
}

유니티의 이벤트 함수인 Start()에서 runNativeCode()를 불러 스크립트가 실행될 때 실행되도록 해주었고,
실제 runNativeCode() 안에는 별다른 로직 없이 플랫폼 래핑과 분기처리만 해주었습니다.

❗️주의: 플랫폼 래핑 코드를 Start() 안에 쓰면 NoEntry 에러가 나니 꼭 runNativeCode() 안에서 해주어야 합니다.

여기까지 코드를 작성했다면, 전체 코드는 다음과 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;

public class Showcase : MonoBehaviour 
{
	#if UNITY_IOS
    	[DllImport("__Internal")]
        private static extern void NativeiOSCode_runNativeCode();
    #endif
    
    void Start()
    {
    	runNativeCode();
    }
    
    private void runNativeCode()
    {
    	#if UNITY_IOS
        	NativeiOSCode_runNativeCode();
        #else
        	Debug.Log("No iOS Device Found");
        #endif
    }
}

9) Unity 빌드

다음은 Unity에서 iOS 플랫폼으로 빌드를 해주면 됩니다. (컴퓨터에 Xcode가 설치되어 있어야 합니다)

만약 Xcode에서 signing 설정이 안 되어 있다면 로그인 후 프로젝트 세팅에서 Signing & Capabilities 설정을 해줍니다.

설정 후 실행시켜보면 우리가 Swift로 쓴 함수가 실행돼 로그가 잘 찍히고 있는 모습을 확인할 수 있습니다.


2. Swift Framework -> Bridge -> Unity Plugin

두 번째 방법은 이 미디엄에서 설명하고 있는 방법입니다.

1) Xcode에서 iOS Framework 생성

새로운 프로젝트를 생성해봅시다.
iOS > Framework를 클릭하고 마음에 드는 이름으로 프로젝트를 생성합니다.


2) Bridge 생성

자동으로 Source와 Products가 생성되어 있을 텐데요, (최근 Xcode 업데이트로 Products 파일이 생략되었을 수 있으나 중요하지 않습니다. 없는 상태로 계속 진행하셔도 됩니다.)
Source 폴더에서 이미 생성되어 있는 [프레임워크이름].swift 파일 이외에 [프레임워크이름]Bridge.mm 파일과 [프레임워크이름]-Bridging-Header.h 파일을 생성해주어야 합니다.

[프레임워크이름]Bridge.mm 파일은 조금 헷갈릴 수 있는데, cmd + n 혹은 New > File을 누른 후 Objective-C 파일을 선택하면 .m 파일이 생성됩니다. 그럼 파일 이름 변경을 눌러 .mm 파일로 바꿔주면 됩니다.

.mm 파일은 Objective-C를 컴파일 할 때 C++로 컴파일 되고, .m 파일은 C로 컴파일 된다는 차이가 있습니다. (위키피디아 참고)


3) Swift 메서드 작성

위에서 소스 파일을 세 개 만들었는데요, 순서대로 코드를 작성해보겠습니다.

[.swift 파일] - 실제 로직
[프레임워크이름].swift 파일에 원하는 로직의 메서드를 작성합니다.

import Foundation

@objc public class UnityPlugin : NSObject {
    
    @objc public static let shared = UnityPlugin()
    @objc public func AddTwoNumber(a:Int,b:Int ) -> Int {
        
        let result = a+b;
        return result;
    }
}

싱글톤 객체로 선언하였고, 내부 swift 로직이 아닌 objc-briding-header에 의해 읽힐 메서드이니 @objc 키워드를 붙여주었습니다.


[.mm 파일] - 유니티와 소통할 수 있게 하는 브릿지
.mm 파일은 이렇게 작성해주었습니다.

#import <Foundation/Foundation.h>
#include "UnityFramework/UnityFramework-Swift.h"

extern "C" {
    
#pragma mark - Functions
    
    int _addTwoNumberInIOS(int a , int b) {
       
        int result = [[UnityPlugin shared] AddTwoNumberWithA:(a) b:(b)];
        return result;
    }
}

extern "C"는 C++ (.cpp) 또는 Objective-C++ (.mm)를 사용하여 플러그인을 실행할 경우 네임 맹글링 문제를 피하기 위해 함수를 C링크에 선언하는 방법입니다.

그 후 함수 이름을 선언하고, 싱글톤으로 선언된 .swift 파일에서 만들었던 메서드를 불러주었습니다.
여기서 매개변수와 리턴값은 유니티의 호출 코드와 소통합니다.


[.h 파일] - 헤더 파일
.h 파일은 생성하면 코드가 자동으로 입력되어 있을 텐데요, 아래처럼 잘 생성이 되었다면 건들지 않아도 됩니다.

#ifndef [프레임워크이름]_Bridging_Header_h
#define [프레임워크이름]_Bridging_Header_h


#endif /* [프레임워크이름]_Bridging_Header_h */

4) 유니티 프로젝트에 플러그인 세팅

유니티 프로젝트의 Assets 폴더 안에 Plugins라는 폴더를 생성합니다.
그 안에 iOS 폴더를 생성하고, Xcode에서 작성한 Source 파일 전체를 해당 폴더 안으로 가져옵니다. Source 폴더 안에 우리가 Xcode에서 만든 3개의 파일이 있는 상태여야 합니다.
그 후 Editor 폴더를 추가하고, SwiftPostProcess.cs 파일을 생성해줍니다.


5) SwiftPostProcess.cs 설정

위에서 생성한 SwiftPostProcess.cs 파일로 들어갑니다.

그리고 그 파일에 아래 코드를 복사해서 붙여넣어주세요.
[프레임워크이름] 부분을 아까 Xcode에서 생성한 자신의 프레임워크 이름으로 바꿔주면 됩니다. 제 경우에는 UnityIosPlugin입니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.Diagnostics;

using System.IO;
using System.Linq;

public static class SwiftPostProcess
{

    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
    {
        if (buildTarget == BuildTarget.iOS)
        {
            var projPath = buildPath + "/Unity-Iphone.xcodeproj/project.pbxproj";
            var proj = new PBXProject();
            proj.ReadFromFile(projPath);

            var targetGuid = proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
            
            proj.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");

            proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/Plugins/iOS/[프레임워크이름]/Source/UnityPlugin-Bridging-Header.h");

            proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_INTERFACE_HEADER_NAME", "[프레임워크이름]-Swift.h");

            proj.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks $(PROJECT_DIR)/lib/$(CONFIGURATION) $(inherited)");
            proj.AddBuildProperty(targetGuid, "FRAMERWORK_SEARCH_PATHS",
                "$(inherited) $(PROJECT_DIR) $(PROJECT_DIR)/Frameworks");
            proj.AddBuildProperty(targetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
            proj.AddBuildProperty(targetGuid, "DYLIB_INSTALL_NAME_BASE", "@rpath");
            proj.AddBuildProperty(targetGuid, "LD_DYLIB_INSTALL_NAME",
                "@executable_path/../Frameworks/$(EXECUTABLE_PATH)");
            proj.AddBuildProperty(targetGuid, "DEFINES_MODULE", "YES");
            proj.AddBuildProperty(targetGuid, "SWIFT_VERSION", "4.0");
            proj.AddBuildProperty(targetGuid, "COREML_CODEGEN_LANGUAGE", "Swift");

            proj.WriteToFile(projPath);
        }
    }

}

이 파일이 없으면 매번 iOS 빌드를 하고 난 후에 pbxproj(프로젝트 세팅 파일)에 들어가서 빌드 프로퍼티를 수정, 생성 해줘야 하는데요, PostProcessBuild를 통해 빌드가 끝나면 자동으로 빌드 프로퍼티를 입력해주는 역할을 합니다.

이 파일에서 objc bridging header의 위치를 가리켜주었고, 제대로 실행될 수 있게 해주었습니다.

혹 권한이라든지 프로젝트 세팅 파일에 더 설정해주어야 할 내용이 생긴다면 proj.WriteToFile(projPath); 위에 적어주면 됩니다.


6) Unity C# Script에서 Swift 메서드 호출

이제 원하는 스크립트 파일에서 스위프트 함수를 불러주기만 하면 됩니다.

패키지를 사용했던 첫번째 방법과 마찬가지로 코드를 작성해보겠습니다. 위와 같은 로직이므로 설명은 하지 않습니다.

using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

public class PluginHelper : MonoBehaviour
{
	#if UNITY_IOS
        [DllImport("__Internal")]
        private static extern int _addTwoNumberInIOS(int a, int b);
	#endif
    
    void Start()
    {
        AddTwoNumber();
    }

    public void AddTwoNumber()
    {
    	#if UNITY_IOS
            int result = _addTwoNumberInIOS(10, 5);
            Debug.log(result)
        #else
        	Debug.Log("No iOS Device Found");
        #endif
    }
}

7) Unity 빌드

유니티에서 iOS를 하고 나면 플러그인에 우리가 만든 프레임워크가 플러그인으로 잘 들어가있는 걸 볼 수 있습니다.

Xcode에 로그도 잘 찍힙니다.


주의사항) XCode 작업 코드 빌드 전 꼭 Unity로 옮기기

Swift 코드를 수정할 일이 생기면 유니티 에디터에서는 swift 코드 수정을 못 하기 때문에 편의상 iOS 빌드가 된 후 생긴 Xcode 프로젝트 파일로 열게 될 텐데요, 여기서 소스 코드를 수정하고 나면 꼭 빌드 전 Unity 소스파일로 옮겨주어야 합니다. 그냥 빌드를 하면 Xcode 프로젝트 파일은 덮어쓰기가 되기 때문에 힘들게 쓴 코드가 다 날아가는 대참사가 날 수 있습니다. 경험담입니다.


2️⃣ Unity에서 iOS HealthKit 사용하기

여기까지 따라왔다면 Unity에서 Swift 메서드를 부를 수 있는 상태가 되었습니다.
굉장히 자유롭게 Swift 코드를 쓸 수 있는 상태가 된 겁니다.

그럼 원래 우리의 목표였던 대망의 HealthKit 연동을 해보겠습니다. 이미 Swift 연동을 해놓았으므로 Swift로 iOS 네이티브 코드를 구현하던 것과 같이 구현하면 됩니다.

전 위에서 설명한 방법 중 두번째 방법이었던 프레임워크 생성 후 소스코드를 복사하는 방법으로 구현했습니다.

프레임워크 소스코드 중 .swift 파일에 import HealthKit을 해주고 원하는 로직을 작성합니다. Swift로 HealthKit을 다루는 법은 좋은 아티클과 애플이 제공하는 데모코드가 많으므로 여기서 자세하게 다루지는 않겠습니다. 구글에 iOS HealthKit swift 키워드로 찾아보시기를 추천드립니다.

우리가 신경써줘야 할 것은 이 swift 파일에서 HealthKit을 임포트 했을 때 그걸 Unity가 알아들을 수 있냐는 부분인데, 그 설정을 SwiftPostProcess.cs 파일에서 해주면 됩니다.

파일에 아래 코드를 추가해줍니다.

var manager = new ProjectCapabilityManager(projPath, "Entitlements.entitlements", null, proj.GetUnityMainTargetGuid());
manager.AddHealthKit();
manager.WriteToFile();

Entitlement에 HealthKit을 추가해주는 방법으로, .AddHealthKit()은 유니티가 iOS와의 호환성을 위해 Capability를 쉽게 등록할 수 있도록 제공하는 메서드입니다. 만약 HealthKit이 아닌 다른 Kit을 등록하고자 할 때도 마찬가지로 manager.Add~로 쓸 수 있습니다.

이 부분이 추가된 SwiftPostProcess.cs의 전체코드는 다음과 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.Diagnostics;

using System.IO;
using System.Linq;

public static class SwiftPostProcess
{

    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
    {
        if (buildTarget == BuildTarget.iOS)
        {
            var projPath = buildPath + "/Unity-Iphone.xcodeproj/project.pbxproj";
            var proj = new PBXProject();
            proj.ReadFromFile(projPath);

            var targetGuid = proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
            
            proj.AddFrameworkToProject(targetGuid, "HealthKit.framework", false);
            
            proj.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");
            
            proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/10Etc/Plugins/iOS/EmbeddedSwift/Source/EmbeddedSwift-Bridging-Header.h");

            proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_INTERFACE_HEADER_NAME", "EmbeddedSwift-Swift.h");
            
            proj.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks $(PROJECT_DIR)/lib/$(CONFIGURATION) $(inherited)");
            proj.AddBuildProperty(targetGuid, "FRAMERWORK_SEARCH_PATHS",
                "$(inherited) $(PROJECT_DIR) $(PROJECT_DIR)/Frameworks");
            proj.AddBuildProperty(targetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
            proj.AddBuildProperty(targetGuid, "DYLIB_INSTALL_NAME_BASE", "@rpath");
            proj.AddBuildProperty(targetGuid, "LD_DYLIB_INSTALL_NAME",
                "@executable_path/../Frameworks/$(EXECUTABLE_PATH)");
            proj.AddBuildProperty(targetGuid, "DEFINES_MODULE", "YES");
            proj.AddBuildProperty(targetGuid, "SWIFT_VERSION", "4.0");
            proj.AddBuildProperty(targetGuid, "COREML_CODEGEN_LANGUAGE", "Swift");

            proj.WriteToFile(projPath);
            
            var manager = new ProjectCapabilityManager(projPath, "Entitlements.entitlements", null, proj.GetUnityMainTargetGuid());
            manager.AddHealthKit();
            manager.WriteToFile();
        }
    }
}

또, 권한 요청이 있을 경우에 관련 설명을 추가해주어야 하는데, 이 역시도 PostProcess에서 해주면 간편합니다.

rootDict.SetString("NSHealthShareUsageDescription", "You can check your exercise record on Apple Fitness");
rootDict.SetString("NSHealthUpdateUsageDescription", "You can check your exercise record on Apple Fitness");

이렇게 적어주면 iOS 빌드가 되었을 때 세팅 파일에 잘 등록되어 있는 모습을 볼 수 있습니다.





이렇게 하면 Unity에서 iOS의 HealthKit 코드를 Swift로 적기의 대장정이 끝이 납니다.

profile
요즘은 https://welcometodannas.tistory.com/에 더 많은 글을 씁니다.

0개의 댓글