Processingで電子アート入門
Processingという電子アート用の開発環境を試してみました。
インストール
下からアプリをダウンロードします。
実装
円を描画
Processingを起動すると以下のような画面になっています。
ここにコードを書いていきます。
Processingでは主に下の2メソッドに処理を書いていきます。
setup
は最初に一度だけ呼ばれるメソッド、draw
は毎秒呼ばれるメソッドです。
void setup() { } void draw() { }
円の描画はellipseメソッドを使います。
第一引数、第二引数は中心のxyで第三引数と第四引数は横幅と縦幅になります。
void setup() { ellipse(50, 50, 40, 40); }
実行すると以下のように円が表示されます。
背景色を変える場合はfillメソッドを使います。
void setup() { fill(51, 51, 204); ellipse(50, 50, 40, 40); }
線を引く
線を引くにはLineメソッドを使います。
最初の2つの引数が始点の座標、次の2つが終点の座標です。
void setup() { line(10, 30, 90, 70); }
線の色はstrokeメソッドで変更します。
void setup() { stroke(51, 51, 204); line(10, 30, 90, 70); }
文字を表示する
文字の表示はtextメソッドを使います。
void setup() { text("Hello!!", 50, 50); }
クリックイベントを取得する
クリックしているかどうかはmousePressedを使えば取得できます。
drawは毎秒呼ばれるので、ここでmousePressedを使った判定式を書けばクリック時に処理させる事ができます。
void draw() { if (mousePressed) ellipse(50, 50, 40, 40); }
参考URL
【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の設定を行います。
イベント送信は下のようにlogEventで行います。
Analytics.logEvent("ログ1", parameters: [AnalyticsEventTutorialBegin: "Value" as NSObject])
FirebaseAnalyticsの画面には以下のように表示されます。
ユーザープロパティーという、GoogleAnalyticsのカスタムディメンションのような機能もあります。
ユーザープロパティーのセットは下のように行います。
Analytics.setUserProperty("テスト1", forName: "testProperty") Analytics.logEvent("ログ1", parameters: [AnalyticsEventTutorialBegin: "Value" as NSObject])
これを使えばイベントをフィルタリングする事ができます。
【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) } }
これを実装して下に引っ張ると下のようにローディング表示されます。
下に引っ張った時のコールバックメソッドは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: "てきすと")
表示は下の通りです。
User Notifications frameworkでローカル通知を送ってみる
iOS10から使えるようになったUser Notifications frameworkを試してみました。
通知の許可を得る
まずはXcodeにUser Notifications frameworkを追加します。
許可を得る処理は以下の通りです。
引数で使いたいオプションを指定します。
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { result, error in })
これは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)
上では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]) }
通知のキャンセルは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) } }
次に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が上方向に移動するようになりました。
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が回転するようになりました。
Storyboardの多言語対応
Storyboardの多言語対応を試してみました。
まずはプロジェクトに言語設定を追加します。
プロジェクト設定で言語を選択して下さい。
下のような画面になるので、多言語対応するStoryboardを選択します。
選択したStoryboardには、以下のように.stringsファイルが作られます。
次はStoryboardにラベルを配置します。
次は日本語用の.stringsファイルに先程配置したラベルの日本語表示を追加します。
上画像のBOM-ZI-JF1
はラベルのObject-IDになります。
下画像の右下の箇所で確認する事ができます。
言語設定が英語の状態でアプリを起動すると下のようになります。
言語設定を日本語にしたら、以下のように切り替わってくれました。
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にインスパイアされて作ったもので、画像のダウンロードが簡単にできるものになっています。
インストール
いつものように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) } }
実行すると以下のように画像が表示されます。
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) } }