しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

フォルダ以下の全プロジェクトでgit pullする

下のように、フォルダ以下に複数プロジェクトがありそれらでgit pullしたい時のtipsです。

f:id:llcc:20170407204122p:plain

コマンドは下の通りです。

ls | xargs -I{} git -C {} pull origin master

xargs -I{}lsの結果を{}と書いた箇所で使えます。
git -Cはフォルダ指定でgitコマンドを実行するので、フォルダ名({})を渡す事で各プロジェクトのgit pullを実行しています。

iPhoneアプリにストリートビューを表示する

GoogleMapsSDKを使ってアプリ上にストリートビューを表示してみました。

準備

最初にGoogleAPIManagerでAPIキーを作成します。
まずは下サイトでGoogle Maps SDK for iOSを有効化します。

console.developers.google.com

次は下ページでAPIキーを作成します。
「認証情報を作成」ボタンでAPIキーを選択します。

console.developers.google.com

f:id:llcc:20170406200818p:plain

実装

次は今作ったキーを元にストリートビューを表示します。

最初にCocoaPodsでGoogleMapsライブラリをインストールします。

target 'MyApp' do
  use_frameworks!

  pod 'GoogleMaps'
end

ライブラリをインストールしたらAPIキーのセットを行います。
AppDelegateを以下のように修正します。

import UIKit
import GoogleMaps

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        GMSServices.provideAPIKey("API_KEY")
        
        return true
    }
}

最後にストリートビューの貼り付けを行います。
ViewControllerを以下のように修正します。

import UIKit
import GoogleMaps

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let panoView = GMSPanoramaView(frame: view.bounds)
        view.addSubview(panoView)
        
        panoView.moveNearCoordinate(CLLocationCoordinate2D(latitude: -33.732, longitude:150.312))
    }
}

これで画面にストリートビューを表示する事ができました。

f:id:llcc:20170406201337p:plain

ビューには様々なオプションがあります。

let panoView = GMSPanoramaView(frame: view.bounds)
view.addSubview(panoView)

panoView.orientationGestures = false // スワイプで方向転換
panoView.zoomGestures = false // ピンチで拡大・縮小
panoView.navigationGestures = false // 矢印タップで移動
panoView.navigationLinksHidden = true // 矢印を非表示

panoView.moveNearCoordinate(CLLocationCoordinate2D(latitude: -33.732, longitude:150.312))

ビューにはDelegateも設定する事ができます。

extension ViewController: GMSPanoramaViewDelegate {
    func panoramaView(_ view: GMSPanoramaView, willMoveToPanoramaID panoramaID: String) {}
    func panoramaView(_ view: GMSPanoramaView, didMoveTo panorama: GMSPanorama?) {}
    func panoramaView(_ view: GMSPanoramaView, didMoveTo panorama: GMSPanorama, nearCoordinate coordinate: CLLocationCoordinate2D) {}
    func panoramaView(_ view: GMSPanoramaView, error: Error, onMoveNearCoordinate coordinate: CLLocationCoordinate2D) {}
    func panoramaView(_ view: GMSPanoramaView, error: Error, onMoveToPanoramaID panoramaID: String) {}
    func panoramaView(_ panoramaView: GMSPanoramaView, didMove camera: GMSPanoramaCamera) {}
    func panoramaView(_ panoramaView: GMSPanoramaView, didTap point: CGPoint) {}
    func panoramaView(_ panoramaView: GMSPanoramaView, didTap marker: GMSMarker) -> Bool { return true }
    func panoramaViewDidStartRendering(_ panoramaView: GMSPanoramaView) {}
    func panoramaViewDidFinishRendering(_ panoramaView: GMSPanoramaView) {}
}

Processingで電子アート入門

Processingという電子アート用の開発環境を試してみました。

Processing.org

インストール

下からアプリをダウンロードします。

Download \ Processing.org

実装

円を描画

Processingを起動すると以下のような画面になっています。
ここにコードを書いていきます。

f:id:llcc:20170403222211p:plain

Processingでは主に下の2メソッドに処理を書いていきます。
setupは最初に一度だけ呼ばれるメソッド、drawは毎秒呼ばれるメソッドです。

void setup() {
}

void draw() {
}

円の描画はellipseメソッドを使います。
第一引数、第二引数は中心のxyで第三引数と第四引数は横幅と縦幅になります。

void setup() {
  ellipse(50, 50, 40, 40);
}

実行すると以下のように円が表示されます。

f:id:llcc:20170403222801p:plain

背景色を変える場合はfillメソッドを使います。

void setup() {
  fill(51, 51, 204);
  ellipse(50, 50, 40, 40);
}

f:id:llcc:20170403223656p:plain

線を引く

線を引くにはLineメソッドを使います。
最初の2つの引数が始点の座標、次の2つが終点の座標です。

void setup() {
  line(10, 30, 90, 70);
}

f:id:llcc:20170403223906p:plain

線の色はstrokeメソッドで変更します。

void setup() {
  stroke(51, 51, 204);
  line(10, 30, 90, 70);
}

文字を表示する

文字の表示はtextメソッドを使います。

void setup() {
  text("Hello!!", 50, 50);
}

f:id:llcc:20170403224100p:plain

クリックイベントを取得する

クリックしているかどうかはmousePressedを使えば取得できます。
drawは毎秒呼ばれるので、ここでmousePressedを使った判定式を書けばクリック時に処理させる事ができます。

void draw() {
  if (mousePressed)
    ellipse(50, 50, 40, 40);
}

参考URL

プログラミング初心者でも大丈夫!Processingでデジタルアートを作ろう | 株式会社LIG

【ios】FirebaseAnalyticsのイベント送信を試してみる

Firebaseでイベントを送る方法を調べてみました。
GoogleAnalyticsの下処理に相当するものです。

let tracker = GAI.sharedInstance().defaultTracker
tracker?.send(GAIDictionaryBuilder.createEvent(withCategory: "category", action: "action", label: "label", value: NSNumber(value: 1)).build() as? [AnyHashable: Any])

まずは下URLを参考にFirebaseAnalyticsの設定を行います。

www.cl9.info

イベント送信は下のようにlogEventで行います。

Analytics.logEvent("ログ1", parameters: [AnalyticsEventTutorialBegin: "Value" as NSObject])

FirebaseAnalyticsの画面には以下のように表示されます。

f:id:llcc:20170403195453p:plain

ユーザープロパティーという、GoogleAnalyticsのカスタムディメンションのような機能もあります。

f:id:llcc:20170323204353p:plain

ユーザープロパティーのセットは下のように行います。

Analytics.setUserProperty("テスト1", forName: "testProperty")
Analytics.logEvent("ログ1", parameters: [AnalyticsEventTutorialBegin: "Value" as NSObject])

これを使えばイベントをフィルタリングする事ができます。

f:id:llcc:20170403200541p:plain

【UIRefreshControl】UITableViewを下に引っ張ってリロード

UITableViewに下に引っ張るどリロードする実装を試してみました。
こちらはUIRefreshControlというクラスを使います。

UITableViewへのリロード処理の追加

実装は非常に簡単で、UIRefreshControlをUITableViewにaddSubviewするだけです。

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let refreshControl = UIRefreshControl()
        tableView.addSubview(refreshControl)
    }
}

これを実装して下に引っ張ると下のようにローディング表示されます。

f:id:llcc:20170402195402p:plain

下に引っ張った時のコールバックメソッドはvalueChangedイベントを使って設定します。

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let refreshControl = UIRefreshControl()
        refreshControl.addTarget(self, action: #selector(ViewController.refreshControlValueChanged(sender:)), for: .valueChanged)
        tableView.addSubview(refreshControl)
    }
    
    func refreshControlValueChanged(sender: UIRefreshControl) {
        print("テーブルを下に引っ張った時に呼ばれる")
    }
}

処理が終わったら、UIRefreshControlのendRefreshingメソッドを呼んでインジケータを非表示にします。

func refreshControlValueChanged(sender: UIRefreshControl) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
        sender.endRefreshing()
    })
}

UIRefreshControlのカスタマイズ

UIRefreshControlはインジケータの色変更とテキスト追加ができます。

let refreshControl = UIRefreshControl()
refreshControl.tintColor = UIColor.blue
refreshControl.attributedTitle = NSAttributedString(string: "てきすと")

表示は下の通りです。

f:id:llcc:20170402200048p:plain

User Notifications frameworkでローカル通知を送ってみる

iOS10から使えるようになったUser Notifications frameworkを試してみました。

通知の許可を得る

まずはXcodeにUser Notifications frameworkを追加します。

f:id:llcc:20170327230711p:plain

許可を得る処理は以下の通りです。
引数で使いたいオプションを指定します。

UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { result, error in
})

f:id:llcc:20170327231050p:plain

これはiOS9以前での以下の書き方に相当します。

let settings = UIUserNotificationSettings(types: [.badge, .alert, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)

通知を作成する

通知の作成は以下の通りです。
今回は10秒後に起動する通知を作成しました。

let content = UNMutableNotificationContent()
content.title = "たいとる"
content.subtitle = "さぶたいとる"
content.body = "ほんぶん"
content.badge = NSNumber(value: 1)
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "Identifier", content: content, trigger: trigger)
let center = UNUserNotificationCenter.current()
center.add(request)

f:id:llcc:20170327232051p:plain

上ではTimeIntervalを使ってTriggerを作りましたが、DateComponentを使ってTriggerを作る事もできます。

let component = DateComponents(calendar: Calendar.current, year: 2017, month: 3, day: 27, hour: 23, minute: 30)
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: false)

UNUserNotificationCenterにはdelegate

import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        let content = UNMutableNotificationContent()
        content.title = "たいとる"
        let component = DateComponents(calendar: Calendar.current, year: 2017, month: 3, day: 27, hour: 23, minute: 30)
        let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: false)
        let request = UNNotificationRequest(identifier: "Identifier", content: content, trigger: trigger)
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        center.add(request)
        
        return true
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        
        // バックグラウンドで来た通知をタップしてアプリ起動したら呼ばれる
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        // アプリがフォアグラウンドの時に通知が来たら呼ばれる
    }
}

フォアグラウンドで通知が来た時、下のようにcompletionHandlerを実行すれば通知を表示する事ができます。

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
    completionHandler([.alert, .badge, .sound])
}

f:id:llcc:20170327233403p:plain

通知のキャンセルはremoveAllPendingNotificationRequestsメソッドを使います。
removePendingNotificationRequestsメソッドでIDを指定したキャンセルも可能です。

let center = UNUserNotificationCenter.current()
center.removeAllPendingNotificationRequests()
center.removePendingNotificationRequests(withIdentifiers: ["Identifier"])

GameplayKitの「Entities and Components」を試してみる

GameplayKitの「Entities and Components」を試してみました。

Entities and Componentsとは

Entities and Componentsとは、コンポーネント指向プログラミングをサポートするものです。
コンポーネント指向プログラミングとは、そのオブジェクトの性質を継承などではcomponentの追加を使って表すものです。

例えばタップ時にジャンプするオブジェクトにはジャンプコンポーネントを追加を、ランダムに移動するオブジェクトにはランダム移動コンポーネントの追加をすると言った具合です。
そうすることで、簡単に1つのオブジェクトが複数の性質を持たせる事ができます。

Entities and Componentsの実装

早速実装してみます。
今回はNodeを2つ作って、片方は横移動+タップで回転、もう片方は縦移動+タップで回転するようにします。

まずは画面にNodeを2つ表示します。

import SpriteKit
import GameplayKit

class GameScene: SKScene {
    let node1 = SKSpriteNode(color: UIColor(red: 0.1, green: 0.8, blue: 0.8, alpha: 1), size: CGSize(width: 100, height: 100))
    let node2 = SKSpriteNode(color: UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1), size: CGSize(width: 100, height: 100))
    
    override func didMove(to view: SKView) {
        node1.position.x = -100
        node2.position.x =  100
        
        addChild(node1)
        addChild(node2)
    }
}

f:id:llcc:20170322232927p:plain

次にnode1は右方向、node2は上方向に動くようにします。
まずは上方向に移動する性質を表すHorizontalComponentクラスと横方向に移動する性質を表すVerticalComponentクラスを作ります。

class HorizontalComponent: GKComponent {
    let node: SKNode
    
    init(node: SKNode) {
        self.node = node
        
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func update(deltaTime seconds: TimeInterval) {
        node.position.x += 1
    }
}

class VerticalComponent: GKComponent {
    let node: SKNode
    
    init(node: SKNode) {
        self.node = node
        
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func update(deltaTime seconds: TimeInterval) {
        node.position.y += 1
    }
}

続けて今作ったクラスをnode1、node2に適用します。

import SpriteKit
import GameplayKit

class GameScene: SKScene {
    let horizontalComponentSystem = GKComponentSystem(componentClass: HorizontalComponent.self)
    let verticalComponentSystem = GKComponentSystem(componentClass: VerticalComponent.self)
    var lastTime: TimeInterval = 0
    
    let node1 = SKSpriteNode(color: UIColor(red: 0.1, green: 0.8, blue: 0.8, alpha: 1), size: CGSize(width: 100, height: 100))
    let node2 = SKSpriteNode(color: UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1), size: CGSize(width: 100, height: 100))
    let entity1 = GKEntity()
    let entity2 = GKEntity()
    
    override func didMove(to view: SKView) {
        node1.position.x = -100
        node2.position.x =  100
        
        addChild(node1)
        addChild(node2)
        
        let horizontalComponent = HorizontalComponent(node: node1)
        entity1.addComponent(horizontalComponent)
        horizontalComponentSystem.addComponent(horizontalComponent)
        
        let verticalComponent = VerticalComponent(node: node2)
        entity2.addComponent(verticalComponent)
        verticalComponentSystem.addComponent(verticalComponent)
    }
    
    override func update(_ currentTime: TimeInterval) {
        if lastTime == 0 {
            lastTime = currentTime
        }
        let deltaTime = currentTime - lastTime
        horizontalComponentSystem.update(deltaTime: deltaTime)
        verticalComponentSystem.update(deltaTime: deltaTime)
        lastTime = currentTime
    }
}

これでnode1が右方向、node2が上方向に移動するようになりました。

f:id:llcc:20170322235811p:plain

entity1とentity2は下のupdateメソッドのように、片方のコンポーネントから別のコンポーネントのメソッドを呼び出す時に便利です。

class HorizontalComponent: GKComponent {
    let node: SKNode
    
    init(node: SKNode) {
        self.node = node
        
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func update(deltaTime seconds: TimeInterval) {
        node.position.x += 1
        entity?.components.flatMap { $0 as? VerticalComponent }.forEach { $0.update(deltaTime: seconds) }
    }
}

最後に、それぞれのnodeに「画面タップで回転」という性質を追加します。
「画面タップで回転」という性質を表すRotateComponentクラスを下のように作成します。

class RotateComponent: GKComponent {
    let node: SKNode
    
    init(node: SKNode) {
        self.node = node
        
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func rotate() {
        node.run(SKAction.rotate(byAngle: 2, duration: 1))
    }
}

今作ったcomponentをnode1とnode2に反映します。

import SpriteKit
import GameplayKit

class GameScene: SKScene {
    let horizontalComponentSystem = GKComponentSystem(componentClass: HorizontalComponent.self)
    let verticalComponentSystem = GKComponentSystem(componentClass: VerticalComponent.self)
    let rotateComponentSystem = GKComponentSystem(componentClass: RotateComponent.self)
    var lastTime: TimeInterval = 0
    
    let node1 = SKSpriteNode(color: UIColor(red: 0.1, green: 0.8, blue: 0.8, alpha: 1), size: CGSize(width: 100, height: 100))
    let node2 = SKSpriteNode(color: UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1), size: CGSize(width: 100, height: 100))
    let entity1 = GKEntity()
    let entity2 = GKEntity()
    
    override func didMove(to view: SKView) {
        node1.position.x = -100
        node2.position.x =  100
        
        addChild(node1)
        addChild(node2)
        
        let horizontalComponent = HorizontalComponent(node: node1)
        entity1.addComponent(horizontalComponent)
        horizontalComponentSystem.addComponent(horizontalComponent)
        
        let verticalComponent = VerticalComponent(node: node2)
        entity2.addComponent(verticalComponent)
        verticalComponentSystem.addComponent(verticalComponent)
        
        // ここから今回追加
        let rotateComponent1 = RotateComponent(node: node1)
        let rotateComponent2 = RotateComponent(node: node2)
        entity1.addComponent(rotateComponent1)
        entity2.addComponent(rotateComponent2)
        rotateComponentSystem.addComponent(rotateComponent1)
        rotateComponentSystem.addComponent(rotateComponent2)
    }
    
    override func update(_ currentTime: TimeInterval) {
        if lastTime == 0 {
            lastTime = currentTime
        }
        let deltaTime = currentTime - lastTime
        horizontalComponentSystem.update(deltaTime: deltaTime)
        verticalComponentSystem.update(deltaTime: deltaTime)
        lastTime = currentTime
    }
    
    // ここから今回追加
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        rotateComponentSystem.components.flatMap { $0 as? RotateComponent }.forEach {
            $0.rotate()
        }
    }
}

これで画面タップでnodeが回転するようになりました。

f:id:llcc:20170323001027p:plain