しめ鯖日記

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

【Swift3】UIViewのdrawの中で線や文字や画像を描画する

UIViewのdrawメソッド中では線・文字・矩形など様々なものを描画できます。
今回はそれらの描画を試してみました。

検証用コードは下の通りです。

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myView = MyView(frame: view.bounds)
        myView.backgroundColor = UIColor.white
        view.addSubview(myView)
    }
}

class MyView: UIView {
    override func draw(_ rect: CGRect) {
    }
}

線を引く

線の描画は以下の通りです。
線の太さや色を変える事ができます。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 100, y: 100))
        path.addLine(to: CGPoint(x: 200, y: 200))
        path.lineWidth = 5.0 // 線の太さ
        UIColor.brown.setStroke() // 色をセット
        path.stroke()
    }
}

実行結果は以下の通りです。

f:id:llcc:20170503235019p:plain

addLineを追加すれば複雑な線も作る事ができます。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 100, y: 100))
        path.addLine(to: CGPoint(x: 200, y: 200))
        path.addLine(to: CGPoint(x: 300, y: 100)) // 今回追加
        path.lineWidth = 5.0 // 線の太さ
        UIColor.brown.setStroke() // 色をセット
        path.stroke()
    }
}

f:id:llcc:20170503235142p:plain

曲線を描画する

addCurveメソッドを使う事でベジェ曲線を描画する事ができます。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 100, y: 100))
        path.addCurve(to: CGPoint(x: 200, y: 200), controlPoint1: CGPoint(x: 150, y: 100), controlPoint2: CGPoint(x: 200, y: 150))
        path.lineWidth = 5.0 // 線の太さ
        UIColor.brown.setStroke() // 色をセット
        path.stroke()
    }
}

f:id:llcc:20170503235903p:plain

曲線の描画はaddArcでも行う事ができます。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 100, y: 100))
        path.addArc(withCenter: CGPoint(x: 200, y: 100), radius: 100, startAngle: 180 * CGFloat.pi/180, endAngle: 90 * CGFloat.pi/180, clockwise: false)
        path.lineWidth = 5.0 // 線の太さ
        UIColor.brown.setStroke() // 色をセット
        path.stroke()
    }
}

f:id:llcc:20170504000320p:plain

四角形の描画

四角形の描画は以下のように行います。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath(roundedRect: CGRect(x: 100, y: 100, width: 100, height: 100), cornerRadius: 10)
        UIColor.darkGray.setFill() // 色をセット
        path.fill()
    }
}

f:id:llcc:20170504000650p:plain

addLineを使って描画する事もできます。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 100, y: 100))
        path.addLine(to: CGPoint(x: 200, y: 100))
        path.addLine(to: CGPoint(x: 200, y: 200))
        path.addLine(to: CGPoint(x: 100, y: 200))
        path.addLine(to: CGPoint(x: 100, y: 100))
        UIColor.darkGray.setFill() // 色をセット
        path.fill()
    }
}

文字の描画

文字の描画は以下のように行います。
第2引数でフォントや文字色の指定もできます。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        "MyText".draw(at: CGPoint(x: 100, y: 100), withAttributes: [
            NSForegroundColorAttributeName : UIColor.blue,
            NSFontAttributeName : UIFont.systemFont(ofSize: 50),
            ])
    }
}

f:id:llcc:20170504001145p:plain

画像の描画

画像もテキスト同様にdrawメソッドを使います。

class MyView: UIView {
    override func draw(_ rect: CGRect) {
        UIImage(named: "sample")?.draw(at: CGPoint(x: 100, y: 100))
    }
}

f:id:llcc:20170504001341p:plain

WKWebViewのキャッシュなどをクリアする

個人で出しているブラウザアプリの容量がかなりの大きさになっていたので調査しました。

キャッシュ等の削除方法は下の通りです。
これでアプリ容量が500MB → 60MBまで減りました。

WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: Date(timeIntervalSince1970: 0), completionHandler: {})

「キャッシュ・cookieだけ」のように削除対象を絞りたい場合は第一引数に削除したい対象をセットします。

WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler: {})

キーとして使えるものは以下の通りです。

public let WKWebsiteDataTypeDiskCache: String
public let WKWebsiteDataTypeMemoryCache: String
public let WKWebsiteDataTypeOfflineWebApplicationCache: String
public let WKWebsiteDataTypeCookies: String
public let WKWebsiteDataTypeSessionStorage: String
public let WKWebsiteDataTypeLocalStorage: String
public let WKWebsiteDataTypeWebSQLDatabases: String
public let WKWebsiteDataTypeIndexedDBDatabases: String

【iOS】AdMobネイティブ広告を試してみる

2017/10/17追記

*ネイティブ エクスプレス広告ですが、2018年3月に廃止予定とのアナウンスがありました。

f:id:llcc:20171017153833p:plain

はじめに

AdMobでネイティブ エクスプレス広告というものを試してみました。

広告ユニットの作成

広告ユニット作成は、バナー広告同様にAdMobの管理画面から行います。
右端に"ネイティブ"というものがあるので、それを選択して作成します。

f:id:llcc:20170430231121p:plain

Startボタンを押すと、広告サイズ選択画面に移動します。
今回は"小"を選択しました。

f:id:llcc:20170430231249p:plain

サイズを選択すると、テンプレート選択画面に移動します。

f:id:llcc:20170430231346p:plain

中と大のテンプレートは下の通りです。

f:id:llcc:20170430231628p:plain

f:id:llcc:20170430231641p:plain

最後にデザインをカスタマイズします。

f:id:llcc:20170430232033p:plain

デザインはCSSを使ってカスタマイズする事もできます。

f:id:llcc:20170430232059p:plain

広告の実装

まずはAdMobをCocoaPodsでインストールします。

platform :ios, '9.0'

target 'Rss' do
  use_frameworks!

  pod 'Firebase/Core'
  pod 'Firebase/AdMob'
end

次はStoryboardに広告を追加します。
ビューを追加して、クラスをGADNativeExpressAdViewに設定します。

f:id:llcc:20170501104630p:plain

最後にViewControllerに広告読み込み処理を追加すれば完了です。

class ViewController: UIViewController {
    @IBOutlet weak var adView: GADNativeExpressAdView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        adView.adUnitID = "自分のAdUnitID"
        adView.rootViewController = self
        adView.load(GADRequest())
        adView.delegate = self
        adView.backgroundColor = UIColor.blue
   }
}    

f:id:llcc:20170501105153p:plain

Error: No ad to showエラーが出る場合

GADNativeExpressAdViewビューのサイズが適切でないとError: No ad to showエラーになります。
広告の推奨サイズは以下の通りです。

f:id:llcc:20170501104846p:plain

【iOS10】ローカル通知に画像を添付する | User Notifications framework

User Notifications frameworkではローカル通知に画像を添付できるようなので試してみました。

画像なしの通知を送る

まずは下記事を参考に通常の画像なしのローカル通知を送ってみます。

www.cl9.info

実装は下の通りです。

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound], completionHandler: { result, error in
        })
        
        return true
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        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:20170418202058p:plain

画像付き通知を送る

次は画像付きの通知を送ります。
まずはプロジェクトに画像を追加します。

f:id:llcc:20170418204328p:plain

次にローカル通知に画像を設定します。
通知を送る処理を下のように修正します。

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func applicationDidEnterBackground(_ application: UIApplication) {
        let content = UNMutableNotificationContent()
        content.title = "たいとる"
        content.body = "ほんぶん"
        
        if let path = Bundle.main.path(forResource: "image", ofType: "png") {
            content.attachments = [try! UNNotificationAttachment(identifier: "ID1", url: URL(fileURLWithPath: path), options: nil)]
        }
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
        let request = UNNotificationRequest(identifier: "Identifier", content: content, trigger: trigger)
        let center = UNUserNotificationCenter.current()
        center.add(request)
    }
}

今回追加した下の部分が画像の付与処理になります。
UNMutableNotificationContentのattachmentsプロパティーに渡す事で画像を表示しています。

if let path = Bundle.main.path(forResource: "image", ofType: "png") {
    content.attachments = [try! UNNotificationAttachment(identifier: "ID1", url: URL(fileURLWithPath: path), options: nil)]
}

この状態でアプリ起動&バックグラウンドに移動をすると下のように画像付き通知が送信されます。

f:id:llcc:20170418204659p:plain

UNNotificationAttachmentには動画を指定することもできます。

if let path = Bundle.main.path(forResource: "movie", ofType: "mov") {
    content.attachments = [try! UNNotificationAttachment(identifier: "ID1", url: URL(fileURLWithPath: path), options: nil)]
}

動画は事前にプロジェクトに追加したものを使います。

f:id:llcc:20170418205615p:plain

この状態で通知を開くとしたのように動画を見ることができます。

f:id:llcc:20170418205529p:plain

画像・動画だけでなくサウンドも添付する事ができます。

if let path = Bundle.main.path(forResource: "sound", ofType: "wav") {
    content.attachments = [try! UNNotificationAttachment(identifier: "ID1", url: URL(fileURLWithPath: path), options: nil)]
}

表示は下の通りです。

f:id:llcc:20170418205845p:plain

SFSafariViewControllerとSafariがcookieを共有しているかの調査

2017/8/18追記

iOS11では、SFSafariViewControllerとSafaricookieの共有ができなくなりました。

SFSafariViewControllerとSafaricookieを共有しているかの調査

SFSafariViewControllerはiPhoneSafariとのcookieを共有しているかどうか調査してみました。

SafariでセットしたcookieをSFSafariViewControllerで取得できるか

SafariTwitterでログイン。
その後SFSafariViewControllerを見るとどうなってるかを調べました。

SFSafariViewControllerの表示は下のように実装しました。

import UIKit
import SafariServices

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let c = SFSafariViewController(url: URL(string: "https://twitter.com")!)
        present(c, animated: true, completion: nil)
    }
}

まずはSafariTwitterにログインします。

f:id:llcc:20170418193112p:plain

下が、その状態でSFSafariViewControllerでTwitterを見た時の表示です。
無事にログイン状態になっている(cookieが共有されている)ことが確認できました。

f:id:llcc:20170418193203p:plain

SFSafariViewControllerでセットしたcookieSafariで取得できるか

今度はSFSafariViewControllerでログインしてSafariに移動してみます。
一度ログアウトして、SFSafariViewController上でTwitterにログインします。

f:id:llcc:20170418193412p:plain

この状態でSafariTwitterを見てもログイン状態になっていました。
SFSafariViewController → Safariへもcookieは引き継がれるようです。

f:id:llcc:20170418193459p:plain

WKWebViewではcookieを引き継ぐか

最後にSafariとWKWebViewではcookieの共有されるか確認してみました。
実装は下の通りです。

import UIKit
import WebKit

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let v = WKWebView(frame: view.bounds)
        view.addSubview(v)
        v.load(URLRequest(url: URL(string: "https://twitter.com")!))
    }
}

SFSafariViewController同様にSafariでログイン → WKWebViewで表示としたのですがログイン状態にはなっていませんでした。(cookieは引き継がれず)

f:id:llcc:20170418194947p:plain

まとめ

Safari ⇔ SFSafariViewControllerではcookieが共有される。
Safari ⇔ WKWebViewではcookieが共有されない。

フォルダ以下の全プロジェクトで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) {}
}