我使用的是integrated方式,也就是expo作为主目录,ios应用为子目录。
官方文档
How to add Expo to a native app using the integrated approach
创建expo项目
参考官方文档,创建一个expo项目; 然后把ios棕地应用,迁移到expo项目的ios目录下。
更改Podfile
更改Podfile,尽量不更改每个代码的位置,否则可能出现bug。
如果你的post_install是全局的,可以把config作为全局变量存放,再在post_install中引用。
Ruby
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
require 'json'
platform :ios, '15.1'
install! 'cocoapods',
:deterministic_uuids => false
prepare_react_native_project!
target 'HelloWorld' do
use_expo_modules!
config_command = [
'npx',
'expo-modules-autolinking',
'react-native-config',
'--json',
'--platform',
'ios'
]
config = use_native_modules!(config_command)
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => true,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/..",
:privacy_file_aggregation_enabled => true,
)
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
)
end
end
Podfile中的难点
use_expo_modules!尽量保证在target的最上方,它负责将expo module使用到的各种依赖自动引入。
由于使用了use_expo_modules!,这个时候很可能出现依赖重复引用的问题,根据xcode的Multiple commands produce提示,把你自己引用的pod删除。
例如,比较经典的一个重复引用是expo-image中使用了SDWebImage,而你自己原来也引入了: Pod 'SDWebImage',此时需要删掉自己的引入。
继续参考官方文档
直到Integrating with your code部分
加载expo
我并未使用官方"创建viewController"方案,而是参考官方的最小实现模版,更改了AppDelegate。
如果你的AppDelegate是swift实现的,直接参考它就好了。
由于我的原AppDelegate是用ObjectC实现的,ObjectC无法继承Swift的类,所以只能通过组合的方式加载ExpoAppDelegate。\
原ExpoAppDelegate
swift
import Foundation
import ExpoModulesCore
/**
Allows classes extending `ExpoAppDelegateSubscriber` to hook into project's app delegate
by forwarding `UIApplicationDelegate` events to the subscribers.
Keep functions and markers in sync with https://developer.apple.com/documentation/uikit/uiapplicationdelegate
*/
@objc(EXExpoAppDelegate)
open class ExpoAppDelegate: UIResponder, UIApplicationDelegate {
override public init() {
// The subscribers are initializing and registering before the main code starts executing.
// Here we're letting them know when the `AppDelegate` is being created,
// which happens at the beginning of the main code execution and before launching the app.
ExpoAppDelegateSubscriberRepository.subscribers.forEach {
$0.appDelegateWillBeginInitialization?()
}
super.init()
}
#if os(macOS)
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
#endif
// MARK: - Initializing the App
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, willFinishLaunchingWithOptions: launchOptions)
}
open func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, didFinishLaunchingWithOptions: launchOptions)
}
#elseif os(macOS)
open func applicationWillFinishLaunching(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillFinishLaunching(notification)
}
open func applicationDidFinishLaunching(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidFinishLaunching(notification)
}
// TODO: - Configuring and Discarding Scenes
#endif
// MARK: - Responding to App Life-Cycle Events
#if os(iOS) || os(tvOS)
@objc
open func applicationDidBecomeActive(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(application)
}
@objc
open func applicationWillResignActive(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillResignActive(application)
}
@objc
open func applicationDidEnterBackground(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidEnterBackground(application)
}
open func applicationWillEnterForeground(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillEnterForeground(application)
}
open func applicationWillTerminate(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillTerminate(application)
}
#elseif os(macOS)
@objc
open func applicationDidBecomeActive(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(notification)
}
@objc
open func applicationWillResignActive(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillResignActive(notification)
}
@objc
open func applicationDidHide(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidHide(notification)
}
open func applicationWillUnhide(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillUnhide(notification)
}
open func applicationWillTerminate(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillTerminate(notification)
}
#endif
// MARK: - Responding to Environment Changes
#if os(iOS) || os(tvOS)
open func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidReceiveMemoryWarning(application)
}
#endif
// TODO: - Managing App State Restoration
// MARK: - Downloading Data in the Background
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
#endif
// MARK: - Handling Remote Notification Registration
open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
ExpoAppDelegateSubscriberManager.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
ExpoAppDelegateSubscriberManager.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}
#elseif os(macOS)
open func application(
_ application: NSApplication,
didReceiveRemoteNotification userInfo: [String: Any]
) {
ExpoAppDelegateSubscriberManager.application(application, didReceiveRemoteNotification: userInfo)
}
#endif
// MARK: - Continuing User Activity and Handling Quick Actions
open func application(_ application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, willContinueUserActivityWithType: userActivityType)
}
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
#elseif os(macOS)
open func application(
_ application: NSApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any NSUserActivityRestoring]) -> Void
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
#endif
open func application(_ application: UIApplication, didUpdate userActivity: NSUserActivity) {
return ExpoAppDelegateSubscriberManager.application(application, didUpdate: userActivity)
}
open func application(_ application: UIApplication, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
return ExpoAppDelegateSubscriberManager.application(application, didFailToContinueUserActivityWithType: userActivityType, error: error)
}
#if os(iOS)
open func application(
_ application: UIApplication,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
}
#endif
// MARK: - Background Fetch
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, performFetchWithCompletionHandler: completionHandler)
}
// TODO: - Interacting With WatchKit
// TODO: - Interacting With HealthKit
#endif
// MARK: - Opening a URL-Specified Resource
#if os(iOS) || os(tvOS)
open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return ExpoAppDelegateSubscriberManager.application(app, open: url, options: options)
}
#elseif os(macOS)
open func application(_ app: NSApplication, open urls: [URL]) {
ExpoAppDelegateSubscriberManager.application(app, open: urls)
}
#endif
// TODO: - Disallowing Specified App Extension Types
// TODO: - Handling SiriKit Intents
// TODO: - Handling CloudKit Invitations
// MARK: - Managing Interface Geometry
#if os(iOS)
/**
* Sets allowed orientations for the application. It will use the values from `Info.plist`as the orientation mask unless a subscriber requested
* a different orientation.
*/
open func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return ExpoAppDelegateSubscriberManager.application(application, supportedInterfaceOrientationsFor: window)
}
#endif
}
#endif os(iOS)
}
供AppDelegate.m使用的ExpoAppDelegateBridge.swift
swift
import Foundation
internal import Expo
import React
import ReactAppDependencyProvider
/// Bridge class that wraps ExpoAppDelegate so Objective-C AppDelegate.m can forward
/// UIApplicationDelegate calls to it. Since ObjC cannot inherit from Swift classes,
/// this provides an @objc-compatible wrapper.
@objc(ExpoAppDelegateBridge)
@MainActor
public class ExpoAppDelegateBridge: NSObject {
private let expoDelegate: ExpoAppDelegate
private var window: UIWindow?;
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
var reactNativeFactory: RCTReactNativeFactory?
@objc public override init() {
expoDelegate = ExpoAppDelegate()
super.init()
}
// MARK: - Get ReactNative UIView
@objc public func getReactNativeUIView(_ initialProperties: [String: Any]?) -> UIView {
return reactNativeFactory!.rootViewFactory.view(withModuleName: "main", initialProperties: initialProperties)
}
// MARK: - Push ReactNative UIView to Window,建议重写为自己的实现
@objc public func pushReactNativeViewControllerToWindow(_ initialProperties: [String: Any]?) {
let vc = ExpoViewController(initialProperties);
if ((self.window?.rootViewController?.navigationController) != nil) {
self.window!.rootViewController?.navigationController?.pushViewController(vc, animated: true) ;
} else {
self.window!.rootViewController?.present(vc, animated: true) ;
}
}
// MARK: - Initializing the App
@objc public func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return expoDelegate.application(application, willFinishLaunchingWithOptions: launchOptions)
}
@objc public func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?,
window: UIWindow) -> Bool {
let delegate = ReactNativeDelegate()
let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
self.window = window;
// 启动Expo环境
factory.startReactNative(
withModuleName: "main",
in: nil,
launchOptions: launchOptions
)
return expoDelegate.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// MARK: - Responding to App Life-Cycle Events
@objc public func applicationDidBecomeActive(_ application: UIApplication) {
expoDelegate.applicationDidBecomeActive(application)
}
@objc public func applicationWillResignActive(_ application: UIApplication) {
expoDelegate.applicationWillResignActive(application)
}
@objc public func applicationDidEnterBackground(_ application: UIApplication) {
expoDelegate.applicationDidEnterBackground(application)
}
@objc public func applicationWillEnterForeground(_ application: UIApplication) {
expoDelegate.applicationWillEnterForeground(application)
}
@objc public func applicationWillTerminate(_ application: UIApplication) {
expoDelegate.applicationWillTerminate(application)
}
// MARK: - Responding to Environment Changes
@objc public func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
expoDelegate.applicationDidReceiveMemoryWarning(application)
}
// MARK: - Downloading Data in the Background
@objc public func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
expoDelegate.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
// MARK: - Handling Remote Notification Registration
@objc public func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
expoDelegate.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
@objc public func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
expoDelegate.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}
@objc public func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
expoDelegate.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}
// MARK: - Continuing User Activity and Handling Quick Actions
@objc public func application(_ application: UIApplication,
willContinueUserActivityWithType userActivityType: String) -> Bool {
return expoDelegate.application(application, willContinueUserActivityWithType: userActivityType)
}
@objc public func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Universal Link唤起expo页面
let result = expoDelegate.application(application, continue: userActivity, restorationHandler: restorationHandler) || RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
if (result) {
self.pushReactNativeViewControllerToWindow(nil);
}
return result;
}
@objc public func application(_ application: UIApplication,
didUpdate userActivity: NSUserActivity) {
expoDelegate.application(application, didUpdate: userActivity)
}
@objc public func application(_ application: UIApplication,
didFailToContinueUserActivityWithType userActivityType: String,
error: Error) {
expoDelegate.application(application, didFailToContinueUserActivityWithType: userActivityType, error: error)
}
@objc public func application(_ application: UIApplication,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void) {
expoDelegate.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
}
// MARK: - Background Fetch
@objc public func application(_ application: UIApplication,
performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
expoDelegate.application(application, performFetchWithCompletionHandler: completionHandler)
}
// MARK: - Opening a URL-Specified Resource
@objc public func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
// Scheme唤起expo页面
let result = expoDelegate.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options);
if (result) {
self.pushReactNativeViewControllerToWindow(nil);
}
return result;
}
// MARK: - Managing Interface Geometry
@objc public func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return expoDelegate.application(application, supportedInterfaceOrientationsFor: window)
}
}
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
// Extension point for config-plugins
override func sourceURL(for bridge: RCTBridge) -> URL? {
// needed to return the correct URL for expo-dev-client.
bridge.bundleURL ?? bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
注意点
1.我将factory.startReactNative中的in参数设置为nil,如果需要在打开APP时直接用Expo接管所有页面,请传入UIWindow。
2.使用getReactNativeUIView创建UIView时,可以给React Native传递initialProperties。
3.为了在使用Scheme唤起APP时能正确打开Expo页面,在openUrl生命周期里,使用pushReactNativeViewControllerToWindow唤起Expo页面。
4.如果Scheme和Universal Link唤起APP时候出现问题,可以考虑为Expo页面设定一个前缀,在AppDelegate.m中匹配,然后再打开Expo页面。
在AppDelegate.m中实例化它
AppDelegate.h
Object-C
#import <UIKit/UIKit.h>
// Expo Swift bridge - 用于将 UIApplicationDelegate 事件转发给 ExpoAppDelegate
#import "(YourProjectName)-Swift.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
/// Expo app delegate bridge - 包裹 ExpoAppDelegate 供 ObjC 调用
@property (nonatomic, strong) ExpoAppDelegateBridge *expoBridge;
@end
AppDelegate.m
Object-C
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (instancetype)init {
self = [super init];
if (self) {
// 初始化 Expo 桥接,通知所有 ExpoAppDelegateSubscriber
_expoBridge = [[ExpoAppDelegateBridge alloc] init];
}
return self;
}
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 转发给Expo
return [self.expoBridge application:application willFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 转发给Expo
return [self.expoBridge application:application didFinishLaunchingWithOptions:launchOptions window:self.window];
}
// 通过scheme唤起APP
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
// 转发给 Expo
return [self.expoBridge application:app open:url options:options];
}
// 通过universal link来唤起app
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler{
// 转发给 Expo
return [self.expoBridge application:application continue:userActivity restorationHandler:restorationHandler];
}
// 屏幕旋转
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
UIInterfaceOrientationMask expoMask = [self.expoBridge application:application supportedInterfaceOrientationsFor:window];
if (expoMask != UIInterfaceOrientationMaskAll) {
return expoMask;
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application{
[self.expoBridge applicationWillEnterForeground:application];
}
- (void)applicationDidBecomeActive:(UIApplication *)application{
[self.expoBridge applicationDidBecomeActive:application];
}
- (void)applicationWillResignActive:(UIApplication *)application{
[self.expoBridge applicationWillResignActive:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application{
[self.expoBridge applicationDidEnterBackground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application{
[self.expoBridge applicationWillTerminate:application];
}
/// 内存警告
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[self.expoBridge applicationDidReceiveMemoryWarning:application];
}
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
[self.expoBridge application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
}
#pragma mark - Remote Notification
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// 转发给expo
[self.expoBridge application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
// 转发给expo
[self.expoBridge application:application didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// 转发给expo
[self.expoBridge application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
#pragma mark - User Activity & Quick Actions
- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
// 转发给expo
return [self.expoBridge application:application willContinueUserActivityWithType:userActivityType];
}
- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
// 转发给expo
[self.expoBridge application:application didUpdate:userActivity];
}
- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
// 转发给expo
[self.expoBridge application:application didFailToContinueUserActivityWithType:userActivityType error:error];
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
// 转发给expo
[self.expoBridge application:application performActionFor:shortcutItem completionHandler:completionHandler];
}
@end
创建一个ExpoViewController
ExpoViewController.h
Object-C
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ExpoViewController : UIViewController
// 声明自定义初始化方法
- (instancetype)init:(NSDictionary<NSString *, id> * _Nullable)properties;
@end
NS_ASSUME_NONNULL_END
ExpoViewController.m
Object-C
#import "ExpoViewController.h"
@interface ExpoViewController ()
@property (nonatomic, copy, nullable) NSDictionary *properties;
@end
@implementation ExpoViewController
- (instancetype)init:(NSDictionary<NSString *, id> * _Nullable)properties {
self = [super init];
if (self) {
_properties = properties;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 调用AppDelegate里的expoBridge,获取ReactNativeView,可以在initialProperties里注入token、isComponent、pathname等
self.view = [APP_DELEGATE.expoBridge getReactNativeUIView:self.properties];
// Do any additional setup after loading the view.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end