まいまいワークス

主にiPhoneアプリの開発で考えた事、調べた事、感じた事などを記していきます。

正方形の画像からアイコン画像を生成する

要件検討

  • iOSのアイコンなので単純な角丸形状ではなく、複数のRを用いて直線部分と滑らかに接続された角丸形状にする。
  • 背景と同化する場合があるのでドロップシャドウを軽くかける。

実装方法

単純なRを用いた角丸ではないのでicon.layer.cornerRadiusは使用できない。
また、表示するサイズごとにRの値を計算するのも面倒。
よって、以下の手順で実装

  1. アイコン形状のマスクを用意
  2. マスクを使ってアイコン画像(UIImage)を切り抜き
  3. UIImageViewのimageに切り抜いたUIImageを指定し、シャドウを設定する

マスクの生成

Appleのwebで使用されていたアイコン表示のマスクイメージを拝借しベクタデータを生成。単純にPDF画像を参照しても良いのですが、UIBezierPathから生成。
サイズを指定し、一旦背景を白く塗りつぶした後にpathの形状を黒く塗りつぶす。

import UIKit

class MaskImage: NSObject {

    static func getMask(iconSize: CGFloat) -> UIImage {
        
        let size = CGSize(width: iconSize, height: iconSize)
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        
        let context: CGContext = UIGraphicsGetCurrentContext()!
        context.setFillColor(UIColor.white.cgColor)
        context.fill(CGRect(x: 0, y: 0, width: iconSize, height: iconSize))
        
        let path = self.maskPath()
        path.apply(CGAffineTransform(scaleX: iconSize / 256.0, y: iconSize / 256.0))
        
        UIColor.black.setFill()
        path.lineWidth = 0
        path.fill()
        
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        guard let image = image else {
            return UIImage()
        }
        return image
    }
    
    
    static func maskPath() -> UIBezierPath {
        
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 256, y: 175.9))
        path.addCurve(to: CGPoint(x: 256, y: 185.1), controlPoint1: CGPoint(x: 256, y: 179), controlPoint2: CGPoint(x: 256, y: 182))
        path.addCurve(to: CGPoint(x: 255.9, y: 192.8), controlPoint1: CGPoint(x: 256, y: 187.7), controlPoint2: CGPoint(x: 255.9, y: 190.2))
        path.addCurve(to: CGPoint(x: 254.4, y: 209.6), controlPoint1: CGPoint(x: 255.8, y: 198.4), controlPoint2: CGPoint(x: 255.3, y: 204.1))
        path.addCurve(to: CGPoint(x: 249.1, y: 225.6), controlPoint1: CGPoint(x: 253.4, y: 215.2), controlPoint2: CGPoint(x: 251.7, y: 220.6))
        path.addCurve(to: CGPoint(x: 225.6, y: 249.1), controlPoint1: CGPoint(x: 244, y: 235.7), controlPoint2: CGPoint(x: 235.7, y: 244))
        path.addCurve(to: CGPoint(x: 209.6, y: 254.4), controlPoint1: CGPoint(x: 220.6, y: 251.7), controlPoint2: CGPoint(x: 215.2, y: 253.4))
        path.addCurve(to: CGPoint(x: 192.8, y: 255.8), controlPoint1: CGPoint(x: 204.1, y: 255.3), controlPoint2: CGPoint(x: 198.5, y: 255.8))
        path.addCurve(to: CGPoint(x: 185.1, y: 256), controlPoint1: CGPoint(x: 190.3, y: 255.9), controlPoint2: CGPoint(x: 187.7, y: 256))
        path.addCurve(to: CGPoint(x: 175.9, y: 256), controlPoint1: CGPoint(x: 182, y: 256), controlPoint2: CGPoint(x: 179, y: 256))
        path.addLine(to: CGPoint(x: 80.1, y: 256))
        path.addCurve(to: CGPoint(x: 70.9, y: 256), controlPoint1: CGPoint(x: 77, y: 256), controlPoint2: CGPoint(x: 74, y: 256))
        path.addCurve(to: CGPoint(x: 63.2, y: 255.9), controlPoint1: CGPoint(x: 68.3, y: 256), controlPoint2: CGPoint(x: 65.8, y: 255.9))
        path.addCurve(to: CGPoint(x: 46.4, y: 254.4), controlPoint1: CGPoint(x: 57.6, y: 255.8), controlPoint2: CGPoint(x: 51.9, y: 255.3))
        path.addCurve(to: CGPoint(x: 30.4, y: 249.1), controlPoint1: CGPoint(x: 40.8, y: 253.4), controlPoint2: CGPoint(x: 35.4, y: 251.6))
        path.addCurve(to: CGPoint(x: 6.9, y: 225.6), controlPoint1: CGPoint(x: 20.3, y: 244), controlPoint2: CGPoint(x: 12, y: 235.7))
        path.addCurve(to: CGPoint(x: 1.6, y: 209.6), controlPoint1: CGPoint(x: 4.3, y: 220.6), controlPoint2: CGPoint(x: 2.6, y: 215.2))
        path.addCurve(to: CGPoint(x: 0.1, y: 192.8), controlPoint1: CGPoint(x: 0.7, y: 204), controlPoint2: CGPoint(x: 0.2, y: 198.4))
        path.addCurve(to: CGPoint(x: 0, y: 185.1), controlPoint1: CGPoint(x: 0.1, y: 190.2), controlPoint2: CGPoint(x: 0, y: 187.6))
        path.addCurve(to: CGPoint(x: 0, y: 175.9), controlPoint1: CGPoint(x: 0, y: 182), controlPoint2: CGPoint(x: 0, y: 179))
        path.addLine(to: CGPoint(x: 0, y: 80.1))
        path.addCurve(to: CGPoint(x: 0, y: 70.9), controlPoint1: CGPoint(x: 0, y: 77), controlPoint2: CGPoint(x: 0, y: 74))
        path.addCurve(to: CGPoint(x: 0.1, y: 63.2), controlPoint1: CGPoint(x: 0, y: 68.3), controlPoint2: CGPoint(x: 0.1, y: 65.8))
        path.addCurve(to: CGPoint(x: 1.6, y: 46.4), controlPoint1: CGPoint(x: 0.2, y: 57.6), controlPoint2: CGPoint(x: 0.7, y: 51.9))
        path.addCurve(to: CGPoint(x: 6.9, y: 30.4), controlPoint1: CGPoint(x: 2.6, y: 40.8), controlPoint2: CGPoint(x: 4.3, y: 35.4))
        path.addCurve(to: CGPoint(x: 30.4, y: 6.9), controlPoint1: CGPoint(x: 12, y: 20.3), controlPoint2: CGPoint(x: 20.3, y: 12))
        path.addCurve(to: CGPoint(x: 46.4, y: 1.6), controlPoint1: CGPoint(x: 35.4, y: 4.3), controlPoint2: CGPoint(x: 40.8, y: 2.6))
        path.addCurve(to: CGPoint(x: 63.2, y: 0.1), controlPoint1: CGPoint(x: 51.9, y: 0.7), controlPoint2: CGPoint(x: 57.5, y: 0.2))
        path.addCurve(to: CGPoint(x: 70.9, y: 0), controlPoint1: CGPoint(x: 65.8, y: 0.1), controlPoint2: CGPoint(x: 68.3, y: 0))
        path.addCurve(to: CGPoint(x: 80.1, y: 0), controlPoint1: CGPoint(x: 74, y: 0), controlPoint2: CGPoint(x: 77, y: 0))
        path.addLine(to: CGPoint(x: 175.9, y: 0))
        path.addCurve(to: CGPoint(x: 185.1, y: 0), controlPoint1: CGPoint(x: 179, y: 0), controlPoint2: CGPoint(x: 182, y: 0))
        path.addCurve(to: CGPoint(x: 192.8, y: 0.1), controlPoint1: CGPoint(x: 187.7, y: 0), controlPoint2: CGPoint(x: 190.2, y: 0.1))
        path.addCurve(to: CGPoint(x: 209.6, y: 1.6), controlPoint1: CGPoint(x: 198.4, y: 0.2), controlPoint2: CGPoint(x: 204.1, y: 0.7))
        path.addCurve(to: CGPoint(x: 225.6, y: 6.9), controlPoint1: CGPoint(x: 215.2, y: 2.6), controlPoint2: CGPoint(x: 220.6, y: 4.3))
        path.addCurve(to: CGPoint(x: 249.1, y: 30.4), controlPoint1: CGPoint(x: 235.7, y: 12), controlPoint2: CGPoint(x: 244, y: 20.3))
        path.addCurve(to: CGPoint(x: 254.4, y: 46.4), controlPoint1: CGPoint(x: 251.7, y: 35.4), controlPoint2: CGPoint(x: 253.4, y: 40.8))
        path.addCurve(to: CGPoint(x: 255.8, y: 63.2), controlPoint1: CGPoint(x: 255.3, y: 51.9), controlPoint2: CGPoint(x: 255.8, y: 57.5))
        path.addCurve(to: CGPoint(x: 256, y: 70.9), controlPoint1: CGPoint(x: 255.9, y: 65.8), controlPoint2: CGPoint(x: 255.9, y: 68.3))
        path.addCurve(to: CGPoint(x: 256, y: 80.1), controlPoint1: CGPoint(x: 256, y: 74), controlPoint2: CGPoint(x: 256, y: 77))
        path.addLine(to: CGPoint(x: 256, y: 175.9))
        path.close()
        
        return path
    }
}

取得した画像データからマスクを生成し、UIImageに適用

import UIKit

extension UIImage {
    
    var masking : UIImage? {
        
        let maskImage:UIImage = MaskImage.getMask(iconSize: self.size.width)
        guard let maskImage = maskImage.cgImage else {
            return nil
        }
        
        //マスクを作成する
        let mask = CGImage(maskWidth: maskImage.width,
                           height: maskImage.height,
                           bitsPerComponent: maskImage.bitsPerComponent,
                           bitsPerPixel: maskImage.bitsPerPixel,
                           bytesPerRow: maskImage.bytesPerRow,
                           provider: maskImage.dataProvider!,
                           decode: nil, shouldInterpolate: false)!
        
        //マスクを適用する
        guard let maskedImage = self.cgImage?.masking(mask) else {
            return nil
        }
        
        let resultImage = UIImage(cgImage: maskedImage)
        
        return resultImage
    }
}

UIImageVIewへの適用とドロップシャドウ

import UIKit

extension UIImageView {
    
    /// アイコンエフェクトを適用
    var appIconEffect: UIImageView {
        
        if let iconimage = self.image?.masking {
            self.image = iconimage
        }
        
        // ドロップシャドウを適用
        return self.dropShadow
    }
    
    /// ドロップシャドウを定義 
    var dropShadow: UIImageView {
        
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowRadius = 10.0
        self.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
        self.layer.shadowOpacity = 0.3
        
        return self
    }
}

アイコンのフェクトの適用

その1

@IBOutlet weak var icon: UIImageView!
    
// ViewDidLoadでもViewWillAppearでも適当な場所でどうそ
icon = icon.appIconEffect

その2

let icon = UIImageView(image: UIImage(named: "sampleIcon"))
icon.frame = CGRect(x: 60, y: 280, width: 120, height: 120)
self.view.addSubview(icon.appIconEffect)

もし、シャドウが枠内にしか表示されない場合は... icon.layer.masksToBounds = false これでOK!

アラートダイアログ基本パターン

概要

UIAlertControllerによるダイアログの表示を、汎用クラスを作成し各画面から呼び出せるようにします。
一般的なボタン1つもしくは2つのダイアログを想定。
3つ以上のボタン構成や、テキストフィールド付きのダイアログは使用頻度が低いと考え汎用クラスの要件には含めていません。

基本的なダイアログの記述

let alert = UIAlertController(title:<#title#>, message:<#message#>, preferredStyle:UIAlertController.Style.alert)
        
alert.addAction(UIAlertAction(title: <#buttonTItle#> , style:UIAlertAction.Style.default){
    (action:UIAlertAction)in
    //ボタンが押された時の処理
    
})
        
alert.addAction(UIAlertAction(title: <#cancelButtonTitle#>, style:UIAlertAction.Style.cancel){
    (action:UIAlertAction)in
    //キャンセルボタンが押されたときの処理
    
})

present(alert, animated: true, completion:nil)

<# #>の部分はXcode上だとプレースホルダーとして扱われます。

汎用クラス化

適当なswiftファイルを作成し(ここではCommon.swift)以下のように記述します。
struct~の構成はお好みで。
ButtonTitleをnilにするとボタンが表示されません。またisDestructive:trueにするとボタンが赤字で表示されます(削除などのアクションに使用)
completionでどのボタンを選択したかが返ってきます(cancelならfalse)。nilを設定することもできます。
preferredActionを指定するとボタンが太字になります。状況によって使い分けるようにしてください。
topViewController()で最前面のViewControllerを取得しダイアログを表示します。

import UIKit

struct Common {
    struct Utility {

        /// アラートダイアログ
        /// - Parameters:
        ///   - title: タイトル
        ///   - message: メッセージ
        ///   - okButtonTitle: 右側のボタンタイトル。nilでボタン非表示
        ///   - isDestructive: 右側のボタンスタイル、trueで赤文字になる(削除などのアクション向け)
        ///   - cancelButtonTitle: 左側キャンセルボタンのタイトル。nilでボタン非表示
        ///   - completion: キャンセルボタンタップでfalseを返す。OKボタンタップでtrueを返す。(省略可)
        static func showAlert(title: String?, message: String?, okButtonTitle: String?, isDestructive: Bool, cancelButtonTitle: String?, completion: ((Bool) -> Void)?) {
            let buttonStyle: UIAlertAction.Style!
            if isDestructive == true {
                buttonStyle = .destructive
            } else {
                buttonStyle = .default
            }

            let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)

            if let okButtonTitle = okButtonTitle {
                let OKAction = UIAlertAction(title: okButtonTitle, style: buttonStyle) { (_: UIAlertAction) in
                    if let completion = completion {
                        completion(true)
                    }
                }
                alert.addAction(OKAction)
                alert.preferredAction = OKAction
            }

            if let cancelButtonTitle = cancelButtonTitle {
                let cancelAction = UIAlertAction(title: cancelButtonTitle, style: UIAlertAction.Style.cancel) { (_: UIAlertAction) in
                    if let completion = completion {
                        completion(false)
                    }
                }
                alert.addAction(cancelAction)
            }

            let topview = topViewController()
            if let topview = topview {
                topview.present(alert, animated: true, completion: nil)
            }
        }
            
        /// 最上位のViewControllerを取得
        /// - Returns: UIViewController
        static func topViewController() -> UIViewController? {
            var topViewController: UIViewController?
            if #available(iOS 13.0, *) {
                let scenes = UIApplication.shared.connectedScenes
                let windowScene = scenes.first as? UIWindowScene
                let window = windowScene?.windows.first
                topViewController = window?.rootViewController
            } else {
                topViewController = UIApplication.shared.keyWindow?.rootViewController
            }

            while (topViewController?.presentedViewController) != nil {
                topViewController = topViewController?.presentedViewController
            }

            return topViewController
        }
    }
}

使い方

// ボタンひとつのシンプルなダイアログ
Common.Utility.showAlert(title: "タイトル", message: "メッセージ本文", okButtonTitle:"OK", isDestructive: false, cancelButtonTitle: nil, completion: nil)


// ボタンふたつのパターン
Common.Utility.showAlert(title: "タイトル", message: "メッセージ本文", okButtonTitle: "OK", isDestructive: true, cancelButtonTitle: "Cancel") { status in
    
    if status == true {
        // OKボタン選択時の処理
        
    } else {
        // Cancelボタン選択時の処理
        
    }
}

 

【swift】変数のオプショナル型

swiftの変数は基本的にnilを許容しません。
しかしながら、textFieldやAPI経由で外部から値を取得する場合など、構造上、値がnilになりうる場合があります。

そこで利用されるのがオプショナル型の変数です。
変数をラップする(オプショナル型を使う)をnilを扱うことができ、実際にその変数を使用するためにはアンラップする必要があります。

絶対に値がnilにならない場合

変数宣言時に!を付ける黙示的アンラップ型を使用
自動的にアンラップしてくれるためnilが入ると即落ちる

var aaa:Int! = 10

通常の場合

変数の宣言時に?をつける

var str1:String?
str1 = "zzzz"

アンラップの方法

str1がnilの場合??のあとの項目がresultStrに代入される

var str1:String?
str1 = "zzzz"
let resultStr = (str1 ?? "nilですよ!")
print("result=",resultStr)

str1がnilでなければstr1がstrに代入される。 nilの場合は代入は行われずelse以下が実行される。

if let str = str1 {
    print(str)
}else{
    print("nil!!!!!")
}

【swift】インスタンス変数、クラス変数、ローカル変数

//すべてのクラスから参照可能
let global_let = "global"  //グローバル変数

class myClass: NSObject {

    public let aaa = "abc"     //インスタンス変数(クラス内の共通変数)、インスタンス化すると他クラスから参照可能
    let bbb = "def"                //インスタンス変数(クラス内の共通変数)、インスタンス化すると他クラスから参照可能
    private let ccc = "ghi"        //インスタンス変数(クラス内の共通変数)、他クラスから参照不可
    static let ddd = "jkl"     //クラス変数、他クラスからmyClass.dddで参照可能
    
    func method1(){
        
        let xyz = "xyz"        //ローカル変数
        print("xyz=",xyz)    //メソッド内でのみ有効
    }

}
//上記変数を他のクラスから参考

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        print("global=",global_let)


        let myclass = myClass()       //インスタンス化
        
        print("aaa=",myclass.aaa)
        print("bbb=",myclass.bbb)
//     print("ccc=",myclass.ccc)     //プライベート変数なのでエラーになる
        print("ddd=",myClass.ddd)
        
        
        myclass.method1()       //myClassのmethod1を実行
        
    }

}

//実行結果
global= global
aaa= abc
bbb= def
ddd= jkl
xyz= xyz

【swift】変数を利用する


/** 定数の定義 */
//letで宣言した値は後から変えられない
let aaa = "abc"            //型を宣言しなくても型推論でStringと認識
let bbb:String = "xyz"    //String型を明示

/** 変数の宣言 */
var ccc = 123        //型を宣言しなくても型推論でIntと認識
var ddd:Int = 789   //Int型を明示

ccc = 1234          //変数なので値を変えられる
ddd = 7890          //変数なので値を変えられる

var eee:Int         //変数なので型だけ宣言して値は後から代入できる
eee = 456           //変数なので値を変えられる

var fff              //型の宣言もなく、値の代入もないとエラーになる
fff = 777           //値も型も指定されないとコンパイラも困っちゃう!

print("aaa=",aaa)
print("bbb=",bbb)
print("ccc=",ccc)
print("ddd=",ddd)
print("eee=",eee)


//実行結果
aaa= abc
bbb= xyz
ccc= 1234
ddd= 7890
eee= 456

端末のpush通知設定情報をアプリ側で取得する

メモ

    UIUserNotificationSettings* userConfig = [[UIApplication sharedApplication] currentUserNotificationSettings];
    NSLog(@"cfg=%lu",(unsigned long)userConfig.types);
    
    /** 
     7:通知、バッジ、サウンドOK
     6:通知、サウンドOK、バッジNG
     5:通知、バッジOK、サウンドNG
     4:通知OK、バッジ、サウンドNG
     0:通知NG、なのでバッジ/サウンドもNG
     */

SDWebImageでアニメーションgifとWebPを表示させる

SDWebImageはなかなか優秀な子で、JPEGPNG以外にもGIF、WebP形式の画像も表示できるんですね!
アニメーションGIFを使えば表現の範囲が広がるし、WebPを使うと画像の容量が小さくなるので、サーバーや通信帯域などのインフラ資源を有効に使うことができますね。

GIFの表示

これは簡単!
通常、JPEGPNGファイルを表示しているやり方でURLをGIFファイルのURLに書き換えるだけ。

UIImageView* imageview = [[UIImageView alloc] initWithFrame:CGRectMake(10, 330, 200, 150)];
[imageview sd_setImageWithURL:[NSURL URLWithString:@"https://dl.dropboxusercontent.com/u/36343998/bicycle-gif.gif"] placeholderImage:[UIImage imageNamed:@"noImage.png"] options:SDWebImageCacheMemoryOnly];
[self.view addSubview:imageview];

WebPの表示

これもソース部分はGIFと同じです。

UIImageView* imageview = [[UIImageView alloc] initWithFrame:CGRectMake(10, 30, 275, 184)];
[imageview sd_setImageWithURL:[NSURL URLWithString:@"http://www.gstatic.com/webp/gallery/1.webp"] placeholderImage:[UIImage imageNamed:@"noImage.png"] options:SDWebImageCacheMemoryOnly];
[self.view addSubview:imageview];

但し、WebPの場合は下準備が必用です。

cocoaPodの仕込み

cocoaPodsでWebPはSub specsとなっているのでpodfileは以下のようにWebPを指定します。

pod 'SDWebImage', '~> 3.7.1'
pod 'SDWebImage/WebP', '~> 3.7.1'

Xcodeの仕込み

このままではまだWebPが表示されません。
TARGET>Pods-SDWebImageのPreprocessingの設定でPreprocessor MacrosにSD_WEBP=1と追記します。

f:id:cccookie:20150107153330p:plain

これで、WebPファイルを表示できるようになりました! めでたしめでたし。