MILLEN BOX

音楽好きの組み込みソフトエンジニアによるプログラミング(主にiOSアプリ開発)の勉強の記録

UIButtonにセットした画像のcontentModeが効かない?そんな時の対処方法

お久しぶりの更新です。

通常はいつもMacを使って更新するんですが、実験的にiPhoneはてなブログアプリからアップしてみます。お見苦しい点ありましてもお許し願いたい!!

 

UIButtonにセットしたUIimageViewの画像のcontentModeが効かない問題

ある日、リリースされたばかりアプリを意気揚々と家族(妻)とダウンロードした時のことです。

まず私のiPhone 7では上々に動作。よしよし、いい子だ。

次に妻のiPhone 6s Plusへダウンロードし動作確認の段。

 

ん?

 

何かボタンちっちゃくね?

 

というか、

contentModeの設定きいてなくね??

 

わー!!めっちゃカッコ悪いぞこれー!!

 

すぐに公開停止し原因を探りました。

果たして何が原因だったのでしょうか?

 

実はcontentModeが効いていない訳ではない

いきなり見出しで答えの半分を書いてしまいました。

実はこの通りcontentModeが効いていない訳ではないのです。

このcontentModeなんですが、コレ実は、UIButtonの中にあるUIImageViewのcontentModeなんですね。

つまりこの現象はUIButtonのUIImageViewが適切なサイズになっていないことが原因です。

 

「ん?」

「なんでそんなことが起きるんだ…?」

 

「いや…、ちょっと待てよ…。」

 

「まさか、AutoLayoutの仕業かー!!」

 

はい。その通りです。

AutoLayoutが働くタイミングとcontentModeが効くタイミングによっては、我々が普通に考えるのとは異なる見え方になっちゃうのです。

つまりUIButtonは大きくなっているのに、中のImageViewが小さいままという現象が起きてるんですね。

 

まぁ、以下のリンクの受売りなんですが。

UIButtonの画像をUIButtonに対してAspectFitする方法 - Qiita

 

対策は?

 上記リンクにも答えが書いていますが、UIButtonのcontentHorizontalAlignmentとcontentVerticalAlignmentというプロパティを設定します。

UIImageのアスペクト比を保ったまま拡げる場合に.fillを指定します。

 

(UIButton).contentHorizontalAlignment = .fill

(UIButton).contentVerticalAlignment = .fill

 

という訳で本日は終了です!

 

 

try! Swift Tokyo について

とても行きたいイベントがあります。「try! Swift Tokyo」です。
恐らく日本で開催されるSwiftのイベントでは最大のものと思われます。
Swiftのフジロックみたいなもんでしょう。
本日は try! Swift Tokyo について、私のような若輩者目線で書かせて頂こうと思います。

Swiftコミュニティー主催のカンファレンスです。
素晴らしくかっこいい響きです。カンファレンス。

「まだこのような催しに参加するレベルに私はないよな。」
そのようにお考えの方、ちょっと待って!
どんなにあなたのレベルが低いとしても、講演の内容が全く理解できないということはまずありません。

「ここまでは何となく分かったけど、そこからはチンプンカンプン」

うん。確かにそのようなことはあるでしょう。
ですが、それでいいのです。
このように実感することで、 「現状理解できていることと出来ていないこと」を見える化することが出来たわけです。
それだけではなく、「全然分からないけど何か凄そうなもの」という新しい地平線(ホライゾン)も見ることができるのです!

もしも、講演の内容がまーったく理解できなかったとしましょう。
それでも参加する意味がないことは決してありません。
try! Swift Tokyo にはもちろん沢山の方々が参加されます。
そんな方々に様々な相談をすることができるまたとないチャンスな訳です。
またその中には、普段は会えないようなとても有名な開発者の方も必ずいらっしゃいます。
そんな人とお近づきになれるこれまたまたとないチャンスな訳です! (何てたってSwift界のフジロックなんですから!)

個人的には最終日のハッカソンが気になります。
Swiftプログラマーが集まったハッカソン。決して「結局アイディアソンじゃん」ということはないはずです。
どんなものが出てくるのかとても楽しみです。

そのような環境で受けた刺激はプライスレス!
とても大切なモノに、そして血となり肉となるでしょう。

そんなtry! Swift Tokyo、本日(2017/1/16)の0時まで早割チケットを購入可能です!
(この記事書いている時点で後5時間!!!)
急げ!

www.tryswift.co

ん?お前は参加するのかって??
...私は遠く離れた大阪でサラリーマンをしている身。
検討しましたが、休みを取得するのが難しく今回は断念...! 将来的にサマソニのように東京・大阪同時開催になればいいね(んな訳ない)。

ただ真面目な話、オープンソースコミュニティであるSwiftに少しでも貢献したくこの記事を作成致しました。 Swiftの更なる活性化を願って!

フジロックに毎年今年こそはと行って結局サマソニに行っちゃう若輩Swift愛好家より。

「ポーン - プロフェッショナルな名言をカウントしよう」をリリースしました

2016/12/21に新しいアプリをリリースしました。

  • 名前は「ポーン - プロフェッショナルな名言をカウントしよう」です。
  • お友達が名言を言った際にあの「ポーン」音を鳴らし、これによりドヤ感を演出することが出来ます。
  • また会話の中で何回名言を発言したのかを計測することも可能。(一応本来の機能はカウンタ)
  • 履歴から過去のポーン回数を確認することができます。
  • くだらないアプリですが、誰かの役に立つことが出来れば幸いです。

f:id:anthrgrnwrld:20161226200213p:plain

宜しければ以下からダウンロードお願いします!!

履歴の保存にはRealmを使用しています。
初データベースです。
データベースについての考え方自体ついてはまだまだ勉強しなければならないと感じましたが、Realm自体はとても簡単に使うことが出来ました!

因みにこのアプリもオープンソースにしてみました。

▶︎GitHub - anthrgrnwrld/porrrrrrrrrn

正直今回は今までに比べ、かなりやっつけで作成したのでとても恥ずかしいですが、 誰かの役に立てれば幸いです。

UIDocumentInteractionControllerを使ってOption Menuを表示する [UIDocumentInteractionController] [share] [swift2.3] [swift3]

前回はInstagramへの投稿をやりましたが、今回は何でもアリのUIDocumentInteractionControllerを使用したOption Menuを表示する方法をメモしておきます。

こんなやつです。
f:id:anthrgrnwrld:20161014001005p:plain

Githubは以下です。

▶︎GitHub - anthrgrnwrld/drawWithExpand

1. UIDocumentInteractionControllerDelegate

ViewControllerにUIDocumentInteractionControllerDelegateを追記します。

class ViewController: UIViewController, UIScrollViewDelegate, UIDocumentInteractionControllerDelegate {
    ...
}

2. IBAction内に以下のコードを記述

  • swift2.3
    @IBAction func pressOpenIn(sender: AnyObject) {
        let imageData = UIImageJPEGRepresentation(self.canvasView.image!, 1.0)
        let tmpDirectoryPath = NSTemporaryDirectory()   //tmpディレクトリを取得
        let imageName = "tmp.jpg"
        let imagePath = tmpDirectoryPath + imageName
        let imageURLForOptionMenu = NSURL(fileURLWithPath: imagePath)
        
        do {
            try imageData?.writeToURL(imageURLForOptionMenu, options: .AtomicWrite)
        } catch {
            fatalError("can't save image to tmp directory.")
        }
        
        interactionController = UIDocumentInteractionController(URL: imageURLForOptionMenu)
        interactionController?.delegate = self
        self.interactionController?.UTI = "public.jpg"
        interactionController?.presentOptionsMenuFromRect(self.view.frame, inView: self.view, animated: true)
    }
  • swift3
    @IBAction func pressOpenIn(_ sender: AnyObject) {
        //print("\(NSStringFromClass(self.classForCoder)).\(#function) is called!")
        
        let imageData = UIImageJPEGRepresentation(self.canvasView.image!, 1.0)
        let tmpDirectoryPath = NSTemporaryDirectory()   //tmpディレクトリを取得
        let imageName = "tmp.jpg"
        let imagePath = tmpDirectoryPath + imageName
        imageURLForOptionMenu = URL(fileURLWithPath: imagePath)
        
        do {
            try imageData?.write(to: imageURLForOptionMenu!, options: .atomic)
        } catch {
            fatalError("can't save image to tmp directory.")
        }
        
        interactionController = UIDocumentInteractionController(url: imageURLForOptionMenu!)
        interactionController?.delegate = self
        self.interactionController?.uti = "public.jpg"
        interactionController?.presentOptionsMenu(from: self.view.frame, in: self.view, animated: true)
        
    }
    

utiには開きたいファイルの種類を指定します。
詳細は以下をご参考ください。

developer.apple.com

3. interactionController: UIDocumentInteractionControllerを宣言

このままだとエラーが出てしまいます。
スコープが広いところに以下の宣言を記載します。

var interactionController : UIDocumentInteractionController?

以上で何でもアリのOptionMenuが表示されると思います。

URLスキームを使用したファイルの共有方法については以下のリンクを参照してみてください。

anthrgrnwrld.hatenablog.com

URLスキームを使用してInstagramの投稿ボタンを作成する [UIApplication][share][Photos.framework][swift2.3][swift3]

作成中のアプリでInstagramへの投稿ボタンを付けたいと思いました。
しかし、以前Twitter, Facebookへの投稿機能をSocial.frameworkはInstagramへの投稿機能はありません。

anthrgrnwrld.hatenablog.com

調べたところ、Instagramへの投稿をする方法は大きく2つ存在します。
恐らく公式で一般的なのは UIDocumentInteractionController を使用する方法です。
▶︎参考 [iOS] アプリ内で管理している画像を Instagram へ投稿する | Developers.IO

もう一つの方法はURLスキームを使用した投稿方法です。
今回はこちらを説明します。

なぜURLスキームでの投稿を選択したのか。それはUIDocumentInteractionControllerに比べカッコイイ気がするから!(何となく!!)
ただしこの方法は正式にはサポートしていない方法となります。突然使用できなくなる恐れがありますので注意してください。

f:id:anthrgrnwrld:20161007225720j:plain

今回のGithubは以下です。先日やりました一連のお絵かきアプリの最終系を更新する形で実装しました。

▶︎GitHub - anthrgrnwrld/drawWithExpand at 83920a4df3fdbb11d25d69999270809a88570a6b

1. URLスキームを使ってInstagramを起動する

まずは練習。URLスキームを使ってInstagramを起動してみます。
Xcode8を使用している場合には、何もしていないとこのコードの実行タイミングで落ちます。
info.plistにLSApplicationQueriesSchemesをArray型で追加し、string型のメンバーitem 0をinstagramと指定します。
f:id:anthrgrnwrld:20161007224748p:plain

  • swift2.3
let testURL = NSURL(string: "instagram://")
if UIApplication.sharedApplication().canOpenURL(testURL!) {
    UIApplication.sharedApplication().openURL(testURL!)
}
  • swift3
let testURL = URL(string: "instagram://")
if UIApplication.shared.canOpenURL(testURL!) {
    UIApplication.shared.openURL(testURL!)
}

2. URLスキームを使って指定の画像を選択した状態でInstagramを起動する

URLスキームを使って指定の画像を選択した状態でInstagramを起動するには、指定するURLを以下にすれば可能です。

let testURL = NSURL(string: "instagram://library?LocalIdentifier=" + (投稿したい画像のIDENTIFIER)")

そうすると次の疑問が出てきます。
投稿したい画像のIDENTIFIER ってどうやって取得するの??

3. 投稿したい画像のIDENTIFIERの取得方法

IDENTIFIERはPhotos Frameworkを使って画像を保存する際に同時に取得できます。
画像をカメラロールに保存するだけならUIImageWriteToSavedPhotosAlbumを使用した方法が一番お手軽で良いですが、今回はIDENTIFIER取得の為、Photos Frameworkを使用して画像を保存します。

ProjectのLinked Frameworks and Libraries にPhotos.frameworkを追加します。
そしてプログラムの頭で import Photosと記載しPhoto frameworkをimportするのも忘れずに。

またXcode8を使用している場合にはinfo.plistに”Privacy - Photo Library Usage Description””を追加するのを忘れてはいけません。画像保存の際、写真ライブラリをいじることになるので必要となります。

  • swift2.3
        var imageIdentifier: String?
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
            let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(self.canvasView.image!)
            let placeHolder = createAssetRequest.placeholderForCreatedAsset
            imageIdentifier = placeHolder!.localIdentifier
            }, completionHandler: { (success, error) -> Void in
                print("Finished adding asset.\(success ? "success" : "error")")
                let testURL = NSURL(string: "instagram://library?LocalIdentifier=" + imageIdentifier!)
                if UIApplication.sharedApplication().canOpenURL(testURL!) {
                    UIApplication.sharedApplication().openURL(testURL!)
                }
        })
        
    }
  • swift3
        var imageIdentifier: String?
        PHPhotoLibrary.shared().performChanges({ () -> Void in
            let createAssetRequest = PHAssetChangeRequest.creationRequestForAsset(from: self.canvasView.image!)
            let placeHolder = createAssetRequest.placeholderForCreatedAsset
            imageIdentifier = placeHolder!.localIdentifier
            }, completionHandler: { (success, error) -> Void in
                print("Finished adding asset.\(success ? "success" : "error")")
                let testURL = URL(string: "instagram://library?LocalIdentifier=" + imageIdentifier!)
                if UIApplication.shared.canOpenURL(testURL!) {
                    UIApplication.shared.openURL(testURL!)
                }
        })

上記コードの注意点。
URLスキームの実行は必ずPHPhotoLibrary.shared().performChangesのクロージャーで行うことです。
間違ってこの外で実行してしまうと、imageIdentifierの中がまだ空で、想定通りにURLスキームが実行できません。
写真の保存は別スレッドで行っている為と推測されます。

上記コードをUIButtonのIBAction内などで実行すると以下のようにInstagramのライブラリ選択画面に移行できます。

f:id:anthrgrnwrld:20161007225720j:plain

今回使用した元のお絵かきアプリについて知りたい方は以下も確認してみてください!

anthrgrnwrld.hatenablog.com

anthrgrnwrld.hatenablog.com

anthrgrnwrld.hatenablog.com

anthrgrnwrld.hatenablog.com

swiftでグラデーションがかかったViewを作成する[CAGradientLayer][swift3.0]

今回はグラデーションがかかったViewを作成したいと思います。
現在作成中のアプリで使っていたのですが、ちょっといらないかも...と感じまして、消してしまう前に記録に残しておきます。

githubは以下です。

▶︎GitHub - anthrgrnwrld/gradationView

f:id:anthrgrnwrld:20160923194712p:plain

コードを以下に貼っておきます

class ViewController: UIViewController {

    @IBOutlet weak var gradationView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let gradient = CAGradientLayer()
        gradient.frame = self.view.bounds
        
        let color_1 = UIColor(red: 1.0, green: 0.7, blue: 0.7, alpha: 1.0).cgColor
        let color_2 = UIColor(red: 0.7, green: 1.0, blue: 0.7, alpha: 1.0).cgColor
        let color_3 = UIColor(red: 0.7, green: 0.7, blue: 1.0, alpha: 1.0).cgColor
        gradient.colors = [color_1, color_2, color_3]
        
        let position_1 = NSNumber(value: 0.0 as Float)
        let position_2 = NSNumber(value: 0.5 as Float)
        let position_3 = NSNumber(value: 1.0 as Float)
        
        gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
        gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
        gradient.locations = [position_1, position_2, position_3]
        
        self.gradationView.layer.insertSublayer(gradient, at: 0)

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

処理イメージ

これ書いたの結構前で参考にしたページも忘れてしまいましたが、以下のような処理のイメージです。

(前提方針) CAGradientLayerクラスのインスタンスを色々処理した後に対象のViewのLayerへ代入する。

  1. CAGradientLayerのインスタンスを作成し、その大きさを定義する。
  2. グラデーションする色を決定。いくつ作成しても良い。
  3. グラデーションの開始及び終了の位置、2で決定した色を開始する位置などを決定する。1で決定したサイズを1.0として。
  4. 対象Viewの.layerへ作成・編集したCAGradientLayerのインスタンスを入れる。

以上です。

UIImageの角を丸くするExtension [swift2.2] [UIImage] [CGContextClipToMask]

UIImageの角を丸くしたいと思います。
ネットでザクッと検索をかけると、UIImageViewのCALayer -> cornerRadiusをイジイジすることで実現する方法が多かったのですが、UIImage自体で角を丸くする方法はあんまり出てこなかったので。
とは言いながらやはりネットの記事を参考にしました。(Objective-Cでしたが)

d.akiroom.com

今回はこのページを参考に、swift用いて実現したUIImageのExtentionを備忘録として残しておこうと思います。

実現方法

  1. まずUIBezierPath.init(roundedRect rect: CGRect, cornerRadius: CGFloat)を使用して角が丸いRectを作成します。
  2. 手順1で作成した角丸Rectを青で塗りつぶします。ここで青で塗りつぶされていない部分がマスクになります。
  3. CGContextClipToMask(c: CGContext?, _ rect: CGRect, _ mask: CGImage?)で手順2で作成したマスクを適応します。
  4. drawInRectやdrawAtPointなどで元イメージのDrawを実行します。

ソースは以下に貼っておきます

import UIKit

extension UIImage {
    
    func imageWithCornerRadius(cornerRadius: CGFloat) -> UIImage{
        
        var imageBounds: CGRect
        let scaleForDisplay = UIScreen.mainScreen().scale     //1ポイント当たり何ピクセルか
        let cornerRadius = cornerRadius * scaleForDisplay     //ポイントからピクセルへの変換

        imageBounds = CGRectMake(0, 0, self.size.height, self.size.height)
 
        //角丸のマスクを作成する
        let path = UIBezierPath.init(roundedRect: imageBounds, cornerRadius: cornerRadius)
        UIGraphicsBeginImageContextWithOptions(path.bounds.size, false, 0.0)
        let fillColor = UIColor.blueColor()
        fillColor.setFill()
        path.fill()
        let maskImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        //元イメージにマスクをかける
        UIGraphicsBeginImageContextWithOptions(path.bounds.size, false, 0.0)
        let context = UIGraphicsGetCurrentContext()
        CGContextClipToMask(context, imageBounds, maskImage.CGImage)

        self.drawAtPoint(CGPointZero)
        
        let resultImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return resultImage
        
    }
    
}