しめ鯖日記

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

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

Storyboardの多言語対応

Storyboardの多言語対応を試してみました。

まずはプロジェクトに言語設定を追加します。
プロジェクト設定で言語を選択して下さい。

f:id:llcc:20170318155046p:plain

下のような画面になるので、多言語対応するStoryboardを選択します。

f:id:llcc:20170318155243p:plain

選択したStoryboardには、以下のように.stringsファイルが作られます。

f:id:llcc:20170318155321p:plain

次はStoryboardにラベルを配置します。

f:id:llcc:20170318160556p:plain

次は日本語用の.stringsファイルに先程配置したラベルの日本語表示を追加します。

f:id:llcc:20170318160654p:plain

上画像のBOM-ZI-JF1はラベルのObject-IDになります。
下画像の右下の箇所で確認する事ができます。

f:id:llcc:20170318160640p:plain

言語設定が英語の状態でアプリを起動すると下のようになります。

f:id:llcc:20170318161019p:plain

言語設定を日本語にしたら、以下のように切り替わってくれました。

f:id:llcc:20170318160929p:plain

UISegmentControlやUIButtonは下の方法で対応できました。

"BOM-ZI-JF1.text" = "テストラベル!!";
"2ee-nm-Qy8.segmentTitles[0]" = "セグメント1";
"2ee-nm-Qy8.segmentTitles[1]" = "セグメント2";
"w32-UA-4Oj.normalTitle" = "ボタン";

UIImageViewだけはStoryboardで対応する方法が見つからなかったため、コード上で対応する必要がありそうです。

SDWebImageライクなライブラリ、Kingfisherを使ってみる

Kingfisherというライブラリを使ってみました。
こちらはSDWebImageにインスパイアされて作ったもので、画像のダウンロードが簡単にできるものになっています。

github.com

インストール

いつものようにCocoapodsを使います。

target 'MyApp' do
  use_frameworks!

  pod 'Kingfisher'
end

使い方

以下のように画像の読み込みを行う事ができます。

import UIKit
import Kingfisher

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let v = UIImageView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
        v.kf.setImage(with: URL(string: "https://cdn-ak.f.st-hatena.com/images/fotolife/l/llcc/20151012/20151012161841.png"))
        view.addSubview(v)
    }
}

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

f:id:llcc:20161226230153p:plain

setImageにはプレースホルダー画像、完了後のコールバックなどを渡す事ができます。

import UIKit
import Kingfisher

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let v = UIImageView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
        v.kf.setImage(with: URL(string: "https://cdn-ak.f.st-hatena.com/images/fotolife/l/llcc/20151012/20151012161841.png"), placeholder: #imageLiteral(resourceName: "placeholder"), options: nil, progressBlock: { receivedSize, totalSize in
        }, completionHandler: { image, error, cacheType, imageURL in
        })

        view.addSubview(v)
    }
}

オプションではキャッシュ方法やScaleなどを設定できます。
形式は、以下enumの配列になります。

public enum KingfisherOptionsInfoItem {
    case targetCache(ImageCache)
    case downloader(ImageDownloader)
    case transition(ImageTransition)
    case downloadPriority(Float)
    case forceRefresh
    case forceTransition
    case cacheMemoryOnly
    case onlyFromCache
    case backgroundDecode
    case callbackDispatchQueue(DispatchQueue?)
    case scaleFactor(CGFloat)
    case preloadAllGIFData
    case requestModifier(ImageDownloadRequestModifier)
    case processor(ImageProcessor)
    case cacheSerializer(CacheSerializer)
    case keepCurrentImageWhileLoading
}

setImageにはUIButtonにも追加されています。
こちらは引数にUIControlStateを渡す必要があります。

import UIKit
import Kingfisher

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let b = UIButton(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
        b.kf.setImage(with: URL(string: "https://cdn-ak.f.st-hatena.com/images/fotolife/l/llcc/20151012/20151012161841.png"), for: .normal, placeholder: #imageLiteral(resourceName: "placeholder"), options: nil, progressBlock: { receivedSize, totalSize in
        }, completionHandler: { image, error, cacheType, imageURL in
        })

        view.addSubview(b)
    }
}