iOSシミュレータでURLスキームで起動する方法
iOS11アプリ起動中にPush通知が受け取れない
```
iOS10までは、UserNotificationも上記が呼ばれる
iOS11ではUNUserNotificationCenterDelegateを実装しないと呼ばれない
```
アプリ起動時もUserNotificationを表示させたい場合はcompetionHandler([.badge, .alert, .sound])とすると、表示される。
そのをたっぷすると、上のdidReceiveRemoteNotificationあ呼ばれる
fastlaneでデバイス登録
デバイスの追加
Provisioning Profileの更新
Xcodeの設定の更新
現時点でのFastfaile
```
desc "Register Device"
lane :add_device do |options|
if options[:name] && options[:udid]
register_devices(devices: {options[:name] => options[:udid]})
update_develop_provisioning()
update_adhoc_provisioning()
else
UI.error "Usage: fastlane add_device name:'New device name' udid:'UDID'"
end
end
desc "update developmemt provisioning profile"
lane :update_develop_provisioning do
sigh(
force: true,
development: true,
output_path: "provisioning",
filename: "development.mobileprovision"
)
update_project_provisioning(
build_configuration: "Debug",
profile: "provisioning/development.mobileprovision",
)
end
desc "update adhoc provisioning profile"
lane :update_adhoc_provisioning do
sigh(
force: true,
adhoc: true,
output_path: "provisioning",
filename: "adhoc.mobileprovision"
)
update_project_provisioning(
build_configuration: "Release",
profile: "provisioning/adhoc.mobileprovision",
)
end
```
使い方
fastlane add_device name:'hrk iPhone7' udid:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
iOS circleciのビルド時間を1/3にした話
前提
CircleCIの実行時間は17分から18分
主に時間がかかっているところは
checkout 1分
pod install 5分
build+upload(fabric beta) 9分
やったこと
変更前
変更後
結果
Fabric bataへアプリをアップロードする
プロジェクトに直接インストールしている場合
./Crashlytics.framework/submit API_KEY BUILD_SECRET -ipaPath IAP_PATH
CocoaPodsを使ってインストールしている場合
./Pods/Crashlytics/submit API_KEY BUILD_SECRET -ipaPath IAP_PATH
その他のオプション
Usage:
submit API_KEY BUILD_SECRET default options for upload after archive, with ipa path in environment
additional options:
-help display this message
-ipaPath [path to IPA]
-emails [tester email address],[email]
-groupAliases [group build server alias],[group]
-notesPath [release notes]
-notifications YES|NO
-debug YES|NO
Cocoapods 1.0.0にアップデートした時の対応
0.39.0の記述
platform :ios, "8.0"
use_frameworks!
pod 'Google/Analytics'
pod 'AFNetworking'
pod 'SSKeychain'
pod 'SVProgressHUD'
1.0.0の記述
platform :ios, "8.0"
use_frameworks!
target "Sample" do
pod 'Google/Analytics'
pod 'AFNetworking'
pod 'SSKeychain'
pod 'SVProgressHUD'
end
Acknowledgementsファイルのパスの変更
0.39.0
FileUtils.cp_r('Pods/Target Support Files/Pods-Sample/Pods-Sample-acknowledgements.plist', 'Sample/Settings.bundle/Acknowledgements.plist', :remove_destination => true)
1.0.0
FileUtils.cp_r('Pods/Target Support Files/Pods/Pods-acknowledgements.plist', 'Sample/Settings.bundle/Acknowledgements.plist', :remove_destination => true)
iOS既存プロジェクトにテストを追加する場合のハマりどころ
ある程度開発が進んでから、テストを書こうとするとどこに原因があるのか探すのに時間がかかる
Objective-Cのコードを使っている場合
テストTargetsに「Objective-C Bridging Header」の指定が漏れているCocoaPodsを使ってる
ProjectのConfigurationsにPods.debugの指定が漏れているCocoaPodsのresourceファイルがある
Build Phasesに「Copy Pods Resources」が指定されていない上記の対応をするとうまくテストを実行する事ができました。
今回のプロジェクトはJSONのレスポンスをそのままCoreDataに保存するライブラリを使いました。
https://github.com/hrk-ys/HRKModelTransfer
iOS UIImage+ImageEffectsの調整をするサンプルコード
ほかにもすでにCococaPodsなどでたくさん便利なものが出回っているので、実装自体そんなに大変ではないと思います。
が、デザイナーさんが表現したいぼかし具合を作るのがなかなか難しかったので、実際にパラメータをいじりながら調整できるサンプルコードを書きました。
今回対象にしているのは、Appleのサンプルコードです。
https://developer.apple.com/library/ios/samplecode/UIImageEffects/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013396-Intro-DontLinkElementID_2
実際につくったものはここ
https://github.com/hrk-ys/BlurSample
実装
UIImageEffects.h
+ (UIImage*)imageByApplyingBlurToImage:(UIImage*)inputImagewithRadius:(CGFloat)blurRadiustintColor:(UIColor *)tintColorsaturationDeltaFactor:(CGFloat)saturationDeltaFactormaskImage:(UIImage *)maskImage;
blurRadius、tintColor、saturationDeltaFactorをそれぞれSliderやColor PickerっぽいUIで設定できます
react-nativeのnpmモジュールを作成してみる
Static Libraryの作成
XcodeでNew>Projectをする
iOS、Framework & Libraryを選択し、Cocoa Touch Static Libraryを選択
Header Search Path
$(SRCROOT)/../../React
$(SRCROOT)/../react-native/React
$(SRCROOT)/node_modules/react-native/React
BridgeModuleの作成
githubを参照
https://github.com/hrk-ys/react-native-userdefaults/tree/master/RNUserDefaultsManager
JS
package.json
$ npm init
package名やversionなど適度に変更
{
"name": "react-native-userdefaults",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}
index.js
github参照
https://github.com/hrk-ys/react-native-userdefaults/blob/master/index.js
Example
$ react-native init UserDefaultsExample
package.json
依存モジュールに作成中のモジュールを追加
"dependencies": {
"react-native": "^0.4.4",
"react-native-userdefaults": "file:../"
}
Nativeプロジェクト追加
Library に node_modules/react-native-userdefaults/RNUserDefaults.xcodeproj/ を追加
Linked Frameworks and LibrariesにlibRNUserDefaults.aを追加
開発スタイル
これが正しいのか不明・・・
index.jsやRNUserDefaultsManager.[mh]を更新したら、Exampleプロジェクトの方で、以下を実行
$npm uninstall react-native-userdefaults
$npm install
ReactNativeでImages.xcassetsの画像を使う方法
pod 'React/RCTImage'
使うところは
ただし、そのままではファイルが読み込めないので、
まだまだはまりどころが多いな・・・。
ReactNativeを使ってみたメモ Tips
jsからnativeを呼び出す
参考 http://facebook.github.io/react-native/docs/nativemodulesios.html#content
Obje-C
#import "RCTBridgeModule.h"
@interface SampleManager : NSObject <RCTBridgeModule>
@end
@implementation SampleManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(callFunc:(NSString *)name param:(NSString *)param dict:(NSDictionary*)dict findEvents:(RCTResponseSenderBlock)callback)
{
NSLog(@"name: %@", name);
NSLog(@"str: %@", param);
NSLog(@"dict: %@", dict);
callback(@[ [NSNull null], @{ @"hoge": @"val" } ]);
}
@end
JS
var SampleManager = require('NativeModules').SampleManager;
SampleManager.callFunc(
'action',
'string_param1',
{ foo: 'bar'},
(error, ret) => {
if (error) {
console.error(error);
} else {
console.log(ret);
}
}
);
nativeからjaコードを呼び出す
RCTRootViewやBridgeModuleのインスタンスにbridgeがあるので、それを使う
ここは公式ドキュメントもちょっと間違ってました
Obje-C
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
[self.rootView.bridge.eventDispatcher sendDeviceEventWithName:@"callFuncName"
body:@{@"name": @"foo"}];
JS
var subscription;
ar SimpleApp = React.createClass({
callFromNative: function(params) {
console.log(params);
this.setState({ name: params.name });
},
componentDidMount: function() {
// 登録
subscription = DeviceEventEmitter.addListener('callFuncName', this.callFromNative);
},
componentWillUnmount: function() {
// 解除
subscription.remove();
},
...
});
Nativeで定義したViewを使う
Swift未対応
Obje-C
- RCTViewManagerを継承する
- RCT_EXPORT_MODULE()
- viewメソッドでViewを返す
#import "RCTViewManager.h"
@interface RCTSampleViewManager : RCTViewManager
@end
@implementation RCTSampleViewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
UIView* view = [[UIView alloc] init];
view.frame = CGRectMake(0, 0, 100, 100);
view.backgroundColor = [UIColor greenColor];
UILabel* l = [[UILabel alloc] init];
l.text = @"hogehoge";
l.textColor = [UIColor redColor];
[l sizeToFit];
[view addSubview:l];
return view;
}
@end
JS
SampleView.js
'use strict';
var { requireNativeComponent } = require('react-native');
module.exports = requireNativeComponent('RCTSampleView', null);
index.ios.js
var SampleView = require('./SampleView');
...
render() {
return (
<View style={styles.container}>
<Text>Hello ReactNative!!!</Text>
<SampleView />
</View>
);
}
データの永続化
http://facebook.github.io/react-native/docs/asyncstorage.html#content
Cookie
ネイティブとで使っているCookieを引き継ぐことは可能?
無理やりくっつければ可能
#import "ReactNativeSupport.h"
@implementation ReactNativeSupport
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(requestCookies: (RCTResponseSenderBlock)callback)
{
NSDictionary* cookies = @{ @"session_id" : @"hogehogeho" };
callback(@[ [NSNull null], cookies ]);
}
@end
JS
var cookie;
fetchData() {
var cookie = "";
for (var name in cookies) {
cookie += name + "=" + cookies[name] + ";";
}
fetch(API_URL,
{ method: 'POST',
body: JSON.stringify({"foo":"hoge"}),
headers: {
'cookie': cookie,
}
})
.then((response) => {
console.log(response.headers.map['set-cookie']); // Cookieが取得できる
return response.json();
})
.then((responseData) => {
console.log(responseData);
})
.catch((error) => {
console.warn(error);
});
}
componentDidMount() {
var Support = require('NativeModules').ReactNativeSupport;
Support.requestCookies(
(error, ret) => {
if (error) {
console.error(error);
} else {
cookies = ret;
this.fetchData();
}
}
);
headersで指定しない場合は、responseにset-cookieが入ってきても設定されない。
一度headersで指定すれば、次のアクセスからは指定されている
resourceの画像を使う方法
ベクター画像だとうまく動かなかった
pod 'React/RCTImage'
<Image source={require('image!image_name')} />
ハマりどころ
package.jsonは必要!
cocoapodsで作ったプロジェクトや、Integration with Existing Appで作ったプロジェクトでは packega.jsonを作らないと、別ファイルの読み込みができない。
nodeやってる人には常識かな?
0.4.0以下だとNativeのCustomビューが使えない
コードを細部まで追ってないですが、0.4.1以降を使わないとNativeで定義したViewを使うことができない
ベクター画像は使えない
ドキュメントに書いてないけど読み込めない
ReactNativeを触ってみる
ReactNativeとは
- Viewをコンポーネント単位で表示するためのライブラリ
- Javascriptで書いて、ネイティブのViewでレンダリングされるため高速
実現したいこと
- Appleの審査を待たずにアプリをバージョンアップさせたい
- 一定のパフォーマンスは保ちたい
- アプリ全体ではなく、一部分の置き換え
前提
- 既存システムをReactNativeで置き換える
- ほとんどネイティブの機能を使っていない
検討
- 実はwebviewでもよいかも、パフォーマンスの比較もしたい
導入
- http://www.reactnative.com/
- http://facebook.github.io/react-native/docs/getting-started.html#content
brew install node brew install watchman brew install flow
npm install -g react-native-cli
サンプルプロジェクトの作成
react-native init AwesomeProject
AwesomeProjectディレクトリがつくられる
- AwesomeProject.xcodeproj
- iOS
- node_modules
- package.json起動
Xcodeを立ち上げて、⌘+Rでいつも通り起動サンプルプロジェクトでは、ビルド時にnodeを起動している

レンダリングするjsを指定
レンダリングするjsはアプリにインストールしたファイルからも、webからも取得することが可能// webから取得する場合
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
// アプリ内のファイルを使う場合
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
// 描画させる
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"AwesomeProject"
launchOptions:launchOptions];
アプリ内にファイルを置く場合は以下のコマンドで取得$ curl 'http://localhost:8081/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
Cmd + Rで再読み込みできない場合
Simulator の Hardware > keyboard の設定を確認既存プロジェクトへの導入
CocoaPods
pod 'React'
pod 'React/RCTText'
Bridge-Header.hの追加#import <RCTRootView.h>
iOS App
ViewControllerのviewなどにコードから追加する@IBOutlet weak var wrapView: UIView!
var rootView:RCTRootView? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var jsCodeLocation = NSURL(string:"http://localhost:8081/index.ios.bundle")
rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "SimpleApp", launchOptions: nil)
rootView!.frame = wrapView.bounds
wrapView.addSubview(rootView!)
}
React Native App作成
ReactComponentディレクトリを作り中身は index.ios.js を置く'use strict';
var React = require('react-native');
var {
Text,
View
} = React;
var styles = React.StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'red'
}
});
class SimpleApp extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>This is a simple application.</Text>
</View>
)
}
}
React.AppRegistry.registerComponent('SimpleApp', () => SimpleApp);
開発サーバの起動
(JS_DIR=`pwd`/ReactComponent; cd Pods/React; npm run start -- --root $JS_DIR)
デバッグ
RCTWebSocketDebuggerを追加すると⌘Rで更新ができるpod 'React/RCTWebSocketDebugger
⌘+Ctl+Zでデバッグメニューを表示させるには、シェイクジェスチャーのdelegateを呼ぶ必要があるoverride func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent) {
rootView?.motionEnded(motion, withEvent: event)
}
Swift、UILabelで表示する文字列の高さを取得する
UILabelの高さ取得に気をつけること
2. viewDidLoad時ではまだviewのサイズが決まっていない
ロジック
NSAttributedString
サンプル
NSString
NSAttributedString
パフォーマンス
Swiftで絵文字入りの文字列操作
文字列の扱い
Swiftで文字列はNSStringからStringクラスに変更された文字数
これは結構ネットでもあるのでいいと思うvar str = "Hello"
var len = countElements(str) // 5
文字列の置換
x文字列目まで、x文字以降などを取得するsubstringToIndexやsubstringFromIndexなどは注意が必要(str as NSString).substringFromIndex(3)
絵文字がなければ特に問題ないのだが、絵文字が入るとうまく取得できないadvanceを使ってIndexを取得しそれを使う
var index = advance(str.startIndex, 3)
var str2 = str.substringToIndex(index)
UINavigationControllerをスワイプで遷移させてみる
ナビゲーションコントローラを滑らかに遷移させる
ナビゲーションコントローラの画面遷移をカスタマイズする
主要なクラス
UIViewControllerAnimatedTransitioning
具体的なアニメーションを定義するクラス。実行時間やviewの動きなど。
UIViewControllerInteractiveTransitioning
画面遷移の進捗を把握するクラス?途中経過や中止、完了などを教えてあげれば良きに計らってくれる。 UIPercentDrivenInteractiveTransitionを使うと楽
NavigationControllerDelegate
// pushやpopされたコントローラが渡されるので、適切なアニメーション定義クラスを返す
func navigationController(
navigationController: UINavigationController,
animationControllerForOperation operation: UINavigationControllerOperation,
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
}
// ここは何も気にせずにUIPercentDrivenInteractiveTransitionを返すが良い
func navigationController(navigationController: UINavigationController,
interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
}
実装方法
今回はナビゲーションバーにおけるスワイプでのページ切り替えなので、NavigationControllerを継承したクラスでやってみる
アニメーションの定義
わかりやすく、PushとPopで分けて書く
class PushAnimatedTransitioning : NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// 遷移元のVC
var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
// 遷移先のVC
var toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
// 表示中のView
var containerView = transitionContext.containerView()
var duration:NSTimeInterval = self.transitionDuration(transitionContext)
// アニメーション終了時のframeを取得
toViewController.view.frame = transitionContext.finalFrameForViewController(toViewController)
// 右端から出すため初期値は幅分をプラスする
toViewController.view.center.x += containerView.bounds.width
containerView.addSubview(toViewController.view)
UIView.animateWithDuration(duration,
animations: { () -> Void in
// 先ほどプラスした幅分を戻す
toViewController.view.center.x -= containerView.bounds.width
}, completion: { (Bool) -> Void in
// キャンセルされていなければ完了
transitionContext.completeTransition(!transitionContext.transitionWasCancelled());
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.3
}
}
SwipeNavigationController
delegate
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// 画面遷移するときに使われるアニメーションを返す
func navigationController(navigationController: UINavigationController,
animationControllerForOperation operation: UINavigationControllerOperation,
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .Push {
return PushAnimatedTransitioning()
}
return nil
}
// UIPercentDrivenInteractiveTransitionを返す
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self.interactiveTransition
}
Pan gesture
UINavigationControllerのdelegateで設定。ここは特にどこでも良い
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
var gesture = UIPanGestureRecognizer(target: self, action: "panGesture:")
gesture.delegate = self
viewController.view.addGestureRecognizer(gesture)
}
// 横スワイプのみ対応
func gestureRecognizerShouldBegin(gestureRecognizer: UIPanGestureRecognizer) -> Bool {
var location = gestureRecognizer.translationInView(gestureRecognizer.view!)
if self.nextViewController == nil { return false }
return fabs(location.x) > fabs(location.y)
}
func panGesture(recognizer: UIPanGestureRecognizer) {
var location = recognizer.translationInView(recognizer.view!)
// どれくらい遷移したかを 0 〜 1で数値化
var progress = fabs(location.x / (self.view.bounds.size.width * 1.0));
progress = min(1.0, max(0.0, progress));
// 次の画面が設定してなければ処理は継続しない
if (self.nextViewController == nil) { return }
if (recognizer.state == .Began) {
// 左へのスワイプのみ
if location.x > 0 { return }
self.interactiveTransition = UIPercentDrivenInteractiveTransition()
// ページ遷移させる!!!!!!
self.pushViewController(self.nextViewController!, animated: true)
}
else if (recognizer.state == .Changed) {
// 変化量を通知させる
self.interactiveTransition?.updateInteractiveTransition(progress)
}
else if (recognizer.state == .Ended || recognizer.state == .Cancelled) {
// 終了かキャンセルか
if self.interactiveTransition != nil {
if (progress > 0.5) {
self.interactiveTransition?.finishInteractiveTransition()
self.nextViewController = nil
}
else {
self.interactiveTransition?.cancelInteractiveTransition()
}
}
self.interactiveTransition = nil;
}
}
使い方
storyboardなどでUINavigationControllerのClassをSwipeNavigationControllerにして、 スワイプで遷移させたいViewControllerを設定する。 callbackとかでもよかったけどちょっとめんどくさいかったので。。
if let navi = self.navigationController as? SwipeNavigationController {
navi.nextViewController = vc
}
全体のコードはGithubで。





