iOS」カテゴリーアーカイブ

[Swift3] UIImageから、アスペクト比を維持してサムネイルを生成する

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

概要

既存のUIImageに対して、アスペクト比を維持したままサムネイル(縮小画像)を作成する関数を実装する。
本関数ではWidthを指定し、そのWidthとアスペクト比に合致したHeightを計算してリサイズする。
リサイズは非破壊的に行い、オリジナルのUIImageを維持したまま、リサイズ後のUIImageを新たに生成して返却する。

もっと良い方法がある気がするが、ググってもあまり見つからなかったので自前で実装

コード

/**
 * 横幅を指定してUIImageをリサイズする
 * @params image: 対象の画像
 * @params width: 基準となる横幅
 * @return 横幅をwidthに、縦幅はアスペクト比を保持したサイズにリサイズしたUIImage
*/
func resizeUIImageByWidth(image: UIImage, width: Double) -> UIImage {
  // オリジナル画像のサイズから、アスペクト比を計算
  let aspectRate = image.size.height / image.size.width
  // リサイズ後のWidthをアスペクト比を元に、リサイズ後のサイズを取得
  let resizedSize = CGSize(width: width, height: width * Double(aspectRate))
  // リサイズ後のUIImageを生成して返却
  UIGraphicsBeginImageContext(resizedSize)
  image.draw(in: CGRect(x: 0, y: 0, width: resizedSize.width, height: resizedSize.height))
  let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  return resizedImage!
}

備考

  • パラメータと計算式を微修正すれば、Heightをベースにリサイズする関数も作れる
  • 計算式をそれなりに弄れば、現在のサイズからn%リサイズといった関数も作れる
  • 指定したWidthがオリジナルより大きかったらオリジナルを戻すとか加えたほうが良さそう

[Swift3] フォトアルバム名を指定して、アルバムが存在しなければ新規作成

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

また、以下を満たした状態。以下はググればスグ出る内容なので本記事では割愛

  • PhotosFrameworkを利用できる状態にある
  • ライブラリの利用をユーザから許可を取っている

概要

アルバム名を指定し、そのアルバムが端末内に存在するかをチェックし、存在しない場合に新規作成したい。適当にググって出てきた日本語の参考ページ通りにコードを書いても動かなかったので、公式リファレンスを参考に自前で実装。

コード

/**
* 端末に指定した名称のアルバムを作成する
* ただし既に同名のアルバムが存在する場合は作成しない
* @params albumTitle アルバム名
* @params callback   アルバム生成後に呼び出されるコールバック
*/
func createNewAlbum(albumTitle: String, callback: @escaping (Bool) -> Void) {
  if self.checkAlbumExists(albumTitle: albumTitle) {
    callback(true)
  } else {
    PHPhotoLibrary.shared().performChanges({
      PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumTitle)
    }) { (isSuccess, error) in
      callback(isSuccess)
    }
  }
}

/**
* 端末に指定した名称のアルバムが存在するかを戻す
* @params albumTitle アルバム名
* @return 存在する場合True
*/
func checkAlbumExists(albumTitle: String) -> Bool {
  let albums = PHAssetCollection.fetchAssetCollections(with: PHAssetCollectionType.album, subtype: 
  PHAssetCollectionSubtype.albumRegular, options: nil)
  for i in 0 ..< albums.count {
    let album = albums.object(at: i)
    if album.localizedTitle != nil && album.localizedTitle == albumTitle {
      return true
    }
  }
  return false
}

実行例

“グッチ裕三コレクション”アルバムが存在しない場合に作成する。既に存在していたか、作成に成功した場合に”成功”が出力される

createNewAlbum(albumTitle: "グッチ裕三コレクション") { (isSuccess) in
  if isSuccess {  
    print("成功")
  } else {
    print("失敗")
  }
}

参考

[Swift3] UIImageViewにグリッド線を描画する

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

概要

UIImageViewに以下の様な3×3のグリッド線を描画する。

コード

UIViewを画面いっぱいに貼り付けて、以下のカスタムクラスを作成する

class GridView: UIView {

  override func draw(_ rect: CGRect) {
    let path = UIBezierPath()
    path.lineWidth = 1.5

    UIColor.white.setStroke()
    path.move(   to: getPoint(rect, x: 1, y: 0))
    path.addLine(to: getPoint(rect, x: 1, y: 3))
    path.move(   to: getPoint(rect, x: 2, y: 0))
    path.addLine(to: getPoint(rect, x: 2, y: 3))
    path.move(   to: getPoint(rect, x: 0, y: 1))
    path.addLine(to: getPoint(rect, x: 3, y: 1))
    path.move(   to: getPoint(rect, x: 0, y: 2))
    path.addLine(to: getPoint(rect, x: 3, y: 2))
    path.stroke()
  }

  /* UIImage上の指定した区画の座標を取得 */
  private func getPoint(_ rect: CGRect, x: CGFloat, y: CGFloat) -> CGPoint {
    let width = rect.width / 3.0
    let height = rect.height / 3.0
    return CGPoint(x: width * x, y: height * y)
  }

}

備考

  • 一部数値を書き換えることで3×3にかぎらず、N×Nの任意のグリッド線を描画できる

UIImageの画像を更新しても反映されない

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

問題

例えば以下のような、UIImageViewに描画しているUIImageを動的に書き換える関数がある

public func setImage(newImage: UIImage) {
  self.myImageView.image = newImage
  self.myImageView.setNeedsLayout()
}

通常であれば、この関数を呼び出すとUIImageViewに表示される画像が書き換わるが、
この関数を非同期で呼び出した場合に、変更後の画像が描画されない、もしくは表示までラグが生じてしまう

解決策

どうやら、非同期で画像を更新した場合には、その瞬間に再描画が行われず、別途再描画を行うタイミングで合わせて画像も再描画されるらしい。
とのことなので以下のようにコードを修正し、強制的にメインスレッドで実行することで、非同期で呼び出しても画像が即時更新されるようになった。

public func setImage(newImage: UIImage) {
  DispatchQueue.main.async {
    self.myImageView.image = newImage
    self.myImageView.setNeedsLayout()
  }
}

この問題はUIImageViewに関わらず、UIViewのサブクラス全般で起こりうる問題なので、備忘録として残しておく。

iOS10で最低限のカメラアプリを実装

前提

要素 バージョン
xcode 8.2.1
iOS 10.2

概要

iOS10カスタムカメラ – Swift 3 on @Qiita
について、自分なりに解釈し、一行ずつ理解しながらコードを整理した

plistの設定及びストーリーボードの設定についてはリンク先と同様なので割愛

ソースコード(swift)

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {

  /* カメラを表示するビュー */
  @IBOutlet weak var cameraView: UIView!
  
  /* キャプチャーセッション */
  var captureSesssion: AVCaptureSession!
  
  /* カメラ */
  var stillImageOutput: AVCapturePhotoOutput?
  
  /* プレビューレイヤー */
  var previewLayer: AVCaptureVideoPreviewLayer?
  
  /* 撮影 */
  @IBAction func takeIt(_ sender: Any) {
    
    /* 撮影単位の設定情報を生成 */
    let settingsForMonitoring = AVCapturePhotoSettings()
    
    /* フラッシュの有無(オート) */
    settingsForMonitoring.flashMode = .auto
    
    /* 手ぶれ補正を有効にする(?) */
    settingsForMonitoring.isAutoStillImageStabilizationEnabled = true
    settingsForMonitoring.isHighResolutionPhotoEnabled = false
    
    /* シャッターを切る */
    stillImageOutput?.capturePhoto(with: settingsForMonitoring, delegate: self)
    
  }
  
  /* アプリ起動直前に初期化 */
  override func viewWillAppear(_ animated: Bool) {
    
    /* カメラの初期化 */
    captureSesssion = AVCaptureSession()
    stillImageOutput = AVCapturePhotoOutput()
    
    /* カメラの解像度を1920x1080に設定する */
    captureSesssion.sessionPreset = AVCaptureSessionPreset1920x1080;
    
    /* デバイスを設定(デフォルトのビデオデバイスを選択) */
    let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo);
    
    do {
      
      /* デバイスを取得 */
      let input = try AVCaptureDeviceInput(device: device)
      
      /* デバイスにセッションを入力可能かを検証 */
      if (captureSesssion.canAddInput(input)) {
        
        /* デバイスをセッションに入力 */
        captureSesssion.addInput(input)
        
        /* デバイスを出力可能かを検証 */
        if (captureSesssion.canAddOutput(stillImageOutput)) {
        
          /* デバイスを出力 */
          captureSesssion.addOutput(stillImageOutput)
          
          /* カメラを起動 */
          captureSesssion.startRunning()
          
          /* プレビューレイヤーを初期化 */
          previewLayer = AVCaptureVideoPreviewLayer(session: captureSesssion)
          
          /* レイヤーの引き伸ばし設定(アスペクトフィット) */
          previewLayer?.videoGravity = AVLayerVideoGravityResizeAspect
          
          /* カメラの向きを設定(縦向き) */
          previewLayer?.connection.videoOrientation = AVCaptureVideoOrientation.portrait
          
          /* サブビューにプレビューレイヤーを重ねる */
          cameraView.layer.addSublayer(previewLayer!)
          
          /* サブビュー内のプレビューレイヤーの表示位置を調整 */
          previewLayer?.position = CGPoint(x: self.cameraView.frame.width / 2, y: self.cameraView.frame.height / 2)
          previewLayer?.bounds = cameraView.frame
          
        }
      }
    } catch {
      print(error);
    }
  }
  
  /* 写真が撮影された */
  func capture(
    _ captureOutput: AVCapturePhotoOutput,
    didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?,
    previewPhotoSampleBuffer: CMSampleBuffer?,
    resolvedSettings: AVCaptureResolvedPhotoSettings,
    bracketSettings: AVCaptureBracketedStillImageSettings?,
    error: Error?) {
    
    /* unwrapし、nilじゃないことを検証 */
    if let photoSampleBuffer = photoSampleBuffer {
    
      /* 撮影データをJPEGに変換 */
      let photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
      
      /* JPEG画像をUIImageに変換 */
      let image = UIImage(data: photoData!)
      
      /* UIImageをフォトライブラリに保存 */
      UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
      
      print("hoghog");
      
    }
  }
}

※ シンタックスハイライトは、swiftが対応していなかったので便宜上JavaScriptを設定

実行結果

  • Buttonをタップで撮影
  • 撮影された写真はメディアライブラリに保存

参考

xcodeで実機の利用ができない(0xE80000E2)

前提

xcode 8.2.1
iPhone 6s
iOS 10.2

問題

iPhoneを接続したMacで、xcodeで実機ビルドしようとしたところ、以下のエラーメッセージが表示される。指示に従って画面のロックを解除しても、エラーが変わらず発生し、実機ビルドできない

Please unlock your device and reattach. (0xE80000E2).

解決策

一回接続を解除してiPhoneをロックしてもう一回アンロックして、そしてアンロックした状態のままでもう一回接続し直す。

それでもダメなら下記手順を試す

  1. 設定 → 一般 → リセット
  2. 位置情報とプライバシーをリセット
  3. パスコードを入力してリセットを実行
  4. ケーブルをつなぎ直してビルド
  5. エラーメッセージが表示されなくなる

参考