Merge pull request #23 from krispypen/master

Adding iOS support
This commit is contained in:
Rex Raphael 2019-08-02 11:11:48 +02:00 committed by GitHub
commit 09e9207b95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 544 additions and 174 deletions

View File

@ -8,7 +8,7 @@
[![Watch on GitHub][github-watch-badge]][github-watch]
[![Star on GitHub][github-star-badge]][github-star]
Flutter unity 3D widget for embedding unity in flutter. Add a Flutter widget to show unity. Works on Android, iOS in works.
Flutter unity 3D widget for embedding unity in flutter. Add a Flutter widget to show unity. Works on Android and iOS.
## Installation
First depend on the library by adding this to your packages `pubspec.yaml`:
@ -27,9 +27,10 @@ import 'package:flutter_unity_widget/flutter_unity_widget.dart';
## Preview
Android (30 fps gif, showcasing communication between Flutter and Unity):
30 fps gifs, showcasing communication between Flutter and Unity:
![gif](https://github.com/snowballdigital/flutter-unity-view-widget/blob/master/preview_android.gif?raw=true)
![gif](https://github.com/snowballdigital/flutter-unity-view-widget/blob/master/preview_ios.gif?raw=true)
<br />
@ -60,9 +61,11 @@ Now your project files should look like this.
1. First Open Unity Project.
2. Click Menu: File => Build Settings => Player Settings
2. Click Menu: File => Build Settings
3. Change `Product Name` to Name of the Xcode project, You can find it follow `ios/${XcodeProjectName}.xcodeproj`.
Be sure you have at least one scene added to your build.
3. => Player Settings
**Android Platform**:
1. Make sure your `Graphics APIs` are set to OpenGLES3 with a fallback to OpenGLES2 (no Vulkan)
@ -74,20 +77,22 @@ Now your project files should look like this.
- ARM64 ✅
- x86 ✅
<img src="https://raw.githubusercontent.com/snowballdigital/flutter-unity-view-widget/master/Screenshot%202019-03-27%2007.31.55.png" width="400" />
**IOS Platform**:
1. Other Settings find the Rendering part, uncheck the `Auto Graphics API` and select only `OpenGLES2`.
2. Depending on where you want to test or run your app, (simulator or physical device), you should select the appropriate SDK on `Target SDK`.
**iOS Platform**:
1. This only works with Unity version >=2019.3 because uses Unity as a library!
2. Other Settings find the Rendering part, uncheck the `Auto Graphics API` and select only `OpenGLES3`.
3. Depending on where you want to test or run your app, (simulator or physical device), you should select the appropriate SDK on `Target SDK`.
<br />
<img src="https://raw.githubusercontent.com/snowballdigital/flutter-unity-view-widget/master/Screenshot%202019-03-27%2007.31.55.png" width="400" />
<br />
### Add Unity Build Scripts and Export
Copy [`Build.cs`](https://github.com/snowballdigital/flutter-unity-view-widget/tree/master/scripts/Editor/Build.cs) and [`XCodePostBuild.cs`](https://github.com/f111fei/react-native-unity-demo/blob/master/unity/Cube/Assets/Scripts/Editor/XCodePostBuild.cs) to `unity/<Your Unity Project>/Assets/Scripts/Editor/`
Copy [`Build.cs`](https://github.com/snowballdigital/flutter-unity-view-widget/tree/master/scripts/Editor/Build.cs) and [`XCodePostBuild.cs`](https://github.com/snowballdigital/flutter-unity-view-widget/tree/master/scripts/Editor/XCodePostBuild.cs) to `unity/<Your Unity Project>/Assets/Scripts/Editor/`
Open your unity project in Unity Editor. Now you can export the Unity project with `Flutter/Export Android` (for Unity versions up to 2019.2), `Flutter/Export Android (Unity 2019.3.*)` (for Unity versions 2019.3 and up, which uses the new [Unity as a Library](https://blogs.unity3d.com/2019/06/17/add-features-powered-by-unity-to-native-mobile-apps/) export format), or `Flutter/Export IOS` menu.
@ -123,6 +128,24 @@ IOS will export unity project to `ios/UnityExport`.
}
```
**iOS Platform Only**
1. open your ios/Runner.xcworkspace (workspace!, not the project) in Xcode and add the exported project in the workspace root (with a right click in the Navigator, not on an item -> Add Files to “Runner” -> add the UnityExport/Unity-Iphone.xcodeproj file
<img src="workspace.png" width="400" />
2. Select the Unity-iPhone/Data folder and change the Target Membership for Data folder to UnityFramework
<img src="change_target_membership_data_folder.png" width="400" />
3. Add this to your Runner/Runner/Runner-Bridging-Header.h
```c
#import "UnityUtils.h"
```
4. Add to AppDelegate.swift before the GeneratePluginRegistrant call:
```swift
InitArgs(CommandLine.argc, CommandLine.unsafeArgv)
```
5. Opt-in to the embedded views preview by adding a boolean property to the app's `Info.plist` file with the key `io.flutter.embedded_views_preview` and the value `YES`.
<br />
### AR Foundation (ANDROID only at the moment)
@ -274,7 +297,6 @@ class _UnityDemoScreenState extends State<UnityDemoScreen>{
- `resume()` (Use this to resume unity player)
## Known issues
- no iOS support yet
- Android Export requires several manual changes
- Using AR will make the activity run in full screen (hiding status and navigation bar).

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@ -1,25 +0,0 @@
//
// Created by rex on 19/03/2019.
//
#ifndef FLUTTER_UNITY_WIDGET_FLUTTERUNITY_H
#define FLUTTER_UNITY_WIDGET_FLUTTERUNITY_H
#import <Flutter/Flutter.h>
@interface FlutterUnityController : NSObject <FlutterPlatformView>
- (instancetype)initWithWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
- (UIView*)view;
@end
@interface FlutterUnityFactory : NSObject <FlutterPlatformViewFactory>
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
#endif //FLUTTER_UNITY_WIDGET_FLUTTERUNITY_H

View File

@ -1,87 +0,0 @@
//
// Created by rex on 19/03/2019.
//
#include "FlutterUnity.h"
@implementation FlutterUnityFactory {
NSObject<FlutterBinaryMessenger>* _messenger;
}
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
self = [super init];
if (self) {
_messenger = messenger;
}
return self;
}
@implementation FlutterUnityController {
WKWebView* _webView;
int64_t _viewId;
FlutterMethodChannel* _channel;
}
- (instancetype)initWithWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
if ([super init]) {
_viewId = viewId;
_webView = [[WKWebView alloc] initWithFrame:frame];
NSString* channelName = [NSString stringWithFormat:@"nativeweb_%lld", viewId];
_channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];
__weak __typeof__(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[weakSelf onMethodCall:call result:result];
}];
}
return self;
}
- (UIView*)view {
return _webView;
}
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([[call method] isEqualToString:@"postMessage"]) {
[self postMessage:call result:result];
} else if ([[call method] isEqualToString:@"isReady"]) {
[self postMessage:call result:result];
} else if ([[call method] isEqualToString:@"createUnity"]) {
[self postMessage:call result:result];
} else if ([[call method] isEqualToString:@"pause"]) {
[self postMessage:call result:result];
} else if ([[call method] isEqualToString:@"resume"]) {
[self postMessage:call result:result];
} else {
result(FlutterMethodNotImplemented);
}
}
- (void)postMessage:(FlutterMethodCall*)call result:(FlutterResult)result {
NSString* url = [call arguments];
if (![self postMessage:url]) {
result([FlutterError errorWithCode:@"loadUrl_failed"
message:@"Failed parsing the URL"
details:[NSString stringWithFormat:@"URL was: '%@'", url]]);
} else {
result(nil);
}
}
- (bool)onPostMessage:(NSString*)url {
NSURL* nsUrl = [NSURL URLWithString:url];
if (!nsUrl) {
return false;
}
NSURLRequest* req = [NSURLRequest requestWithURL:nsUrl];
[_webView loadRequest:req];
return true;
}
@end

View File

@ -0,0 +1,18 @@
//
// FlutterUnityView.h
// FlutterUnityView
//
// Created by krispypen on 8/1/2019
//
#import <UIKit/UIKit.h>
#import "UnityUtils.h"
@interface FlutterUnityView : UIView
@property (nonatomic, strong) UIView* uView;
- (void)setUnityView:(UIView *)view;
@end

View File

@ -0,0 +1,37 @@
//
// FlutterUnityView.m
// FlutterUnityView
//
// Created by krispypen on 8/1/2019
//
#import "FlutterUnityView.h"
@implementation FlutterUnityView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
return self;
}
- (void)dealloc
{
}
- (void)setUnityView:(UIView *)view
{
self.uView = view;
[self setNeedsLayout];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[(UIView *)self.uView removeFromSuperview];
[self insertSubview:(UIView *)self.uView atIndex:0];
((UIView *)self.uView).frame = self.bounds;
[(UIView *)self.uView setNeedsLayout];
}
@end

View File

@ -1,4 +1,25 @@
//
// FlutterUnityWidgetPlugin.h
// FlutterUnityWidgetPlugin
//
// Created by Kris Pypen on 8/1/19.
//
#import <Flutter/Flutter.h>
@interface FlutterUnityWidgetPlugin : NSObject<FlutterPlugin>
@end
@interface FUController : NSObject <FlutterPlatformView>
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
- (UIView*)view;
@end
@interface FUViewFactory : NSObject <FlutterPlatformViewFactory>
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar;
@end

View File

@ -1,20 +1,106 @@
//
// FlutterUnityWidgetPlugin.m
// FlutterUnityWidgetPlugin
//
// Created by Kris Pypen on 8/1/19.
//
#import "FlutterUnityWidgetPlugin.h"
#import <flutter_unity_widget/flutter_unity_widget-Swift.h>
#import "UnityUtils.h"
#import "FlutterUnityView.h"
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterNativeWebFactory* webviewFactory =
[[FlutterNativeWebFactory alloc] initWithMessenger:registrar.messenger];
[registrar registerViewFactory:webviewFactory withId:@"unity_view"];
}
/*
#import "FlutterUnityWidgetPlugin.h"
#import <flutter_unity_widget/flutter_unity_widget-Swift.h>
#include <UnityFramework/UnityFramework.h>
@implementation FlutterUnityWidgetPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftFlutterUnityWidgetPlugin registerWithRegistrar:registrar];
FUViewFactory* fuviewFactory = [[FUViewFactory alloc] initWithRegistrar:registrar];
[registrar registerViewFactory:fuviewFactory withId:@"unity_view"];
}
@end
*/
@implementation FUViewFactory {
NSObject<FlutterPluginRegistrar>* _registrar;
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
self = [super init];
if (self) {
_registrar = registrar;
}
return self;
}
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
FUController* controller = [[FUController alloc] initWithFrame:frame
viewIdentifier:viewId
arguments:args
registrar:_registrar];
return controller;
}
@end
@implementation FUController {
FlutterUnityView* _uView;
int64_t _viewId;
FlutterMethodChannel* _channel;
}
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
registrar:(NSObject<FlutterPluginRegistrar>*)registrar {
if ([super init]) {
_viewId = viewId;
NSString* channelName = [NSString stringWithFormat:@"unity_view_%lld", viewId];
_channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:registrar.messenger];
__weak __typeof__(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[weakSelf onMethodCall:call result:result];
}];
}
return self;
}
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([[call method] isEqualToString:@"postMessage"]) {
[self postMessage:call result:result];
} else {
result(FlutterMethodNotImplemented);
}
}
- (void)postMessage:(FlutterMethodCall*)call result:(FlutterResult)result {
NSString* object = [call arguments][@"gameObject"];
NSString* method = [call arguments][@"methodName"];
NSString* message = [call arguments][@"message"];
UnityPostMessage(object, method, message);
result(nil);
}
- (UIView*)view {
_uView = [[FlutterUnityView alloc] init];
if ([UnityUtils isUnityReady]) {
[_uView setUnityView: (UIView*)[GetAppController() unityView]];
} else {
[UnityUtils createPlayer:^{
[_uView setUnityView: (UIView*)[GetAppController() unityView]];
}];
}
return _uView;
}
@end

View File

@ -1,27 +1,23 @@
//
// Created by rex on 15/03/2019.
//
#ifndef FLUTTER_UNITY_WIDGET_UNITYUTILS_H
#define FLUTTER_UNITY_WIDGET_UNITYUTILS_H
#import <Foundation/Foundation.h>
#ifndef UnityUtils_h
#define UnityUtils_h
#ifdef __cplusplus
extern "C" {
#endif
void InitArgs(int argc, char* argv[]);
void InitArgs(int argc, char* argv[]);
bool UnityIsInited(void);
bool UnityIsInited(void);
void InitUnity();
void InitUnity();
void UnityPostMessage(NSString* gameObject, NSString* methodName, NSString* message);
void UnityPostMessage(NSString* gameObject, NSString* methodName, NSString* message);
void UnityPauseCommand();
void UnityPauseCommand();
void UnityResumeCommand();
void UnityResumeCommand();
#ifdef __cplusplus
} // extern "C"
@ -40,5 +36,4 @@ void UnityResumeCommand();
@end
#endif //FLUTTER_UNITY_WIDGET_UNITYUTILS_H
#endif /* UnityUtils_h */

View File

@ -1,10 +1,8 @@
#include "RegisterMonoModules.h"
#include "RegisterFeatures.h"
#include <csignal>
#import <UIKit/UIKit.h>
#import "UnityInterface.h"
#import "UnityUtils.h"
#import "UnityAppController.h"
#include <UnityFramework/UnityFramework.h>
// Hack to work around iOS SDK 4.3 linker problem
// we need at least one __TEXT, __const section entry in main application .o files
@ -18,6 +16,8 @@ char** g_argv;
void UnityInitTrampoline();
UnityFramework* ufw;
extern "C" void InitArgs(int argc, char* argv[])
{
g_argc = argc;
@ -29,6 +29,19 @@ extern "C" bool UnityIsInited()
return unity_inited;
}
UnityFramework* UnityFrameworkLoad()
{
NSString* bundlePath = nil;
bundlePath = [[NSBundle mainBundle] bundlePath];
bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
if ([bundle isLoaded] == false) [bundle load];
UnityFramework* ufw = [bundle.principalClass getInstance];
return ufw;
}
extern "C" void InitUnity()
{
if (unity_inited) {
@ -36,41 +49,30 @@ extern "C" void InitUnity()
}
unity_inited = true;
UnityInitStartupTime();
ufw = UnityFrameworkLoad();
@autoreleasepool
{
UnityInitTrampoline();
UnityInitRuntime(g_argc, g_argv);
RegisterMonoModules();
NSLog(@"-> registered mono modules %p\n", &constsection);
RegisterFeatures();
// iOS terminates open sockets when an application enters background mode.
// The next write to any of such socket causes SIGPIPE signal being raised,
// even if the request has been done from scripting side. This disables the
// signal and allows Mono to throw a proper C# exception.
std::signal(SIGPIPE, SIG_IGN);
}
[ufw setDataBundleId: "com.unity3d.framework"];
[ufw frameworkWarmup: g_argc argv: g_argv];
}
extern "C" void UnityPostMessage(NSString* gameObject, NSString* methodName, NSString* message)
{
UnitySendMessage([gameObject UTF8String], [methodName UTF8String], [message UTF8String]);
dispatch_async(dispatch_get_main_queue(), ^{
[ufw sendMessageToGOWithName:[gameObject UTF8String] functionName:[methodName UTF8String] message:[message UTF8String]];
});
}
extern "C" void UnityPauseCommand()
{
dispatch_async(dispatch_get_main_queue(), ^{
UnityPause(1);
[ufw pause:true];
});
}
extern "C" void UnityResumeCommand()
{
dispatch_async(dispatch_get_main_queue(), ^{
UnityPause(0);
[ufw pause:false];
});
}

View File

@ -15,7 +15,11 @@ Flutter unity 3D widget for embedding unity in flutter
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.frameworks = 'UnityFramework'
s.ios.deployment_target = '8.0'
s.xcconfig = {
'FRAMEWORK_SEARCH_PATHS' => '"${PODS_ROOT}/../UnityExport" "${PODS_ROOT}/../.symlinks/flutter/ios-release" "${PODS_CONFIGURATION_BUILD_DIR}"',
'OTHER_LDFLAGS' => '$(inherited) -framework UnityFramework ${PODS_LIBRARIES}'
}
end

BIN
preview_ios.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 MiB

View File

@ -3,6 +3,8 @@ description: Flutter unity 3D widget for embedding unity in flutter
version: 0.1.5
authors:
- Rex Raphael <rex.raphael@outlook.com>
- Thomas Stockx <thomas@stockxit.com>
- Kris Pypen <kris.pypen@gmail.com>
homepage: https://github.com/snowballdigital/flutter-unity-view-widget/tree/master
environment:

View File

@ -0,0 +1,295 @@
/*
MIT License
Copyright (c) 2017 Jiulong Wang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if UNITY_IOS
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
/// <summary>
/// Adding this post build script to Unity project enables the flutter-unity-widget to access it
/// </summary>
public static class XcodePostBuild
{
/// <summary>
/// The identifier added to touched file to avoid double edits when building to existing directory without
/// replace existing content.
/// </summary>
private const string TouchedMarker = "https://github.com/snowballdigital/flutter-unity-view-widget";
[PostProcessBuild]
public static void OnPostBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS)
{
return;
}
PatchUnityNativeCode(pathToBuiltProject);
UpdateUnityProjectFiles(pathToBuiltProject);
}
/// <summary>
/// We need to add the Data folder to the UnityFramework framework
/// </summary>
private static void UpdateUnityProjectFiles(string pathToBuiltProject)
{
var pbx = new PBXProject();
var pbxPath = Path.Combine(pathToBuiltProject, "Unity-iPhone.xcodeproj/project.pbxproj");
pbx.ReadFromFile(pbxPath);
// Add UnityExport/Data
var targetGuid = pbx.TargetGuidByName("UnityFramework");
var fileGuid = pbx.AddFolderReference(Path.Combine(pathToBuiltProject, "Data"), "Data");
pbx.AddFileToBuild(targetGuid, fileGuid);
pbx.WriteToFile(pbxPath);
}
/// <summary>
/// Make necessary changes to Unity build output that enables it to be embedded into existing Xcode project.
/// </summary>
private static void PatchUnityNativeCode(string pathToBuiltProject)
{
EditUnityFrameworkH(Path.Combine(pathToBuiltProject, "UnityFramework/UnityFramework.h"));
EditUnityAppControllerH(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.h"));
EditUnityAppControllerMM(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.mm"));
}
/// <summary>
/// Edit 'UnityFramework.h': add 'frameworkWarmup'
/// </summary>
private static void EditUnityFrameworkH(string path)
{
var inScope = false;
// Add frameworkWarmup method
EditCodeFile(path, line =>
{
inScope |= line.Contains("- (void)runUIApplicationMainWithArgc:");
if (inScope)
{
if (line.Trim() == "")
{
inScope = false;
return new string[]
{
"",
"// Added by " + TouchedMarker,
"- (void)frameworkWarmup:(int)argc argv:(char*[])argv;",
""
};
}
}
return new string[] { line };
});
}
/// <summary>
/// Edit 'UnityAppController.h': returns 'UnityAppController' from 'AppDelegate' class.
/// </summary>
private static void EditUnityAppControllerH(string path)
{
var inScope = false;
var markerDetected = false;
// Add static GetAppController
EditCodeFile(path, line =>
{
inScope |= line.Contains("- (void)startUnity:");
if (inScope)
{
if (line.Trim() == "")
{
inScope = false;
return new string[]
{
"",
"// Added by " + TouchedMarker,
"+ (UnityAppController*)GetAppController;",
""
};
}
}
return new string[] { line };
});
inScope = false;
markerDetected = false;
// Modify inline GetAppController
EditCodeFile(path, line =>
{
inScope |= line.Contains("extern UnityAppController* GetAppController");
if (inScope && !markerDetected)
{
if (line.Trim() == "")
{
inScope = false;
markerDetected = true;
return new string[]
{
"// }",
"",
"static inline UnityAppController* GetAppController()",
"{",
" return [UnityAppController GetAppController];",
"}",
};
}
return new string[] { "// " + line };
}
return new string[] { line };
});
}
/// <summary>
/// Edit 'UnityAppController.mm': triggers 'UnityReady' notification after Unity is actually started.
/// </summary>
private static void EditUnityAppControllerMM(string path)
{
var inScope = false;
var markerDetected = false;
EditCodeFile(path, line =>
{
if (line.Trim() == "@end")
{
return new string[]
{
"",
"// Added by " + TouchedMarker,
"static UnityAppController *unityAppController = nil;",
"",
@"+ (UnityAppController*)GetAppController",
"{",
" static dispatch_once_t onceToken;",
" dispatch_once(&onceToken, ^{",
" unityAppController = [[self alloc] init];",
" });",
" return unityAppController;",
"}",
"",
line,
};
}
inScope |= line.Contains("- (void)startUnity:");
markerDetected |= inScope && line.Contains(TouchedMarker);
if (inScope && line.Trim() == "}")
{
inScope = false;
if (markerDetected)
{
return new string[] { line };
}
else
{
return new string[]
{
" // Modified by " + TouchedMarker,
@" [[NSNotificationCenter defaultCenter] postNotificationName: @""UnityReady"" object:self];",
"}",
};
}
}
return new string[] { line };
});
inScope = false;
markerDetected = false;
// Modify inline GetAppController
EditCodeFile(path, line =>
{
inScope |= line.Contains("UnityAppController* GetAppController()");
if (inScope && !markerDetected)
{
if (line.Trim() == "}")
{
inScope = false;
markerDetected = true;
return new string[]
{
"",
};
}
return new string[] { "// " + line };
}
return new string[] { line };
});
}
private static void EditCodeFile(string path, Func<string, IEnumerable<string>> lineHandler)
{
var bakPath = path + ".bak";
if (File.Exists(bakPath))
{
File.Delete(bakPath);
}
File.Move(path, bakPath);
using (var reader = File.OpenText(bakPath))
using (var stream = File.Create(path))
using (var writer = new StreamWriter(stream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var outputs = lineHandler(line);
foreach (var o in outputs)
{
writer.WriteLine(o);
}
}
}
}
}
#endif

BIN
workspace.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB