Failed to instantiate the default view controller

[WindowScene] Failed to instantiate the default view controller for UIMainStoryboardFile ‘Main’ – perhaps the designated entry point is not set?

“Is Initial View Controller” がオフになっていると、上記エラーが発生する場合がある。mainstoryboard の最初に起動すべき ViewController の Attribute Inspector から、”Is Initial View Controller” の状態を確認し、オフの場合は、オンにする。

Python + PIL でポスター風画像

変換後の画像
Original Image
""" docustring """
import os
from PIL import Image, ImageOps, ImageEnhance, ImageFilter, ImageChops, ImageDraw

os.chdir("/users/uchukamen/desktop/python/poster/images")
files = os.listdir(".")

for imageFile in files:
    if not imageFile.endswith("jpeg"):
        continue
    print(imageFile)

    origImage = Image.open(imageFile)

    # 1/4 縮小画像を作成
    w, h = origImage.size
    smallSize = (int(w/8), int(h/8))
    smallImage = origImage.resize(smallSize, Image.NEAREST)

    # 色強調 enhancerオブジェクト生成
    enhancer = ImageEnhance.Color(smallImage)
    # enhancerオブジェクトの強調
    enhancedSmallImage = enhancer.enhance(8)

    # 減色
    img_res = enhancedSmallImage.quantize(8)

    # 線画の作成
    maskImage = img_res.convert("L").filter(ImageFilter.CONTOUR)

    # 黒背景画像を作成
    blackImage = Image.new('RGBA', smallSize, 'black')

    # 色強調画像と線画の合成
    mergedImage = Image.composite(img_res, blackImage, maskImage)

    # 色強調 enhancerオブジェクト生成
    enhancer2 = ImageEnhance.Color(mergedImage)
    # enhancerオブジェクトの強調
    enhancedSmallImage = enhancer2.enhance(1)
    enhancedSmallImage.show()

Animation可能な LaunchScreenを表示する方法

LaunchScreen.storyboard はアニメーションできない

アニメーションするLaunchScreen を作ろうとして、LaunchScreen.storyboard に、独自のクラスを適用してみたところ、”Launch screens may not set custom classnames” というエラーが発生する。このため、Launch Screenではアニメーションができない。

起動画面にViewController により、アニメーションを行なう

そこで、起動画面に ViewController によりアニメーションを表示し、アニメーションが終わったら、メインのViewController に遷移する方法を試みる。

遷移先のメインのViewController に Storyboard ID を付与する。

 ここでは、mainViewControllerという Storyboard ID を付与する

LaunchScreen の代わりとなる ViewController を追加する。

SplashViewController.swift を追加し、カスタムクラスを設定・実装する。

SplashViewController は次のとおり。

import UIKit

class SplashViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let nextViewController = storyboard.instantiateViewController(withIdentifier: "mainViewController")
        self.present(nextViewController, animated: true, completion: nil)
    }
}

注意

instantiateViewController をviewDidLoad() から呼び出すと、次の警告が表示されて、Viewの遷移ができない。

Warning: Attempt to present <***.ViewController: 0x104910db0> on <***.SplashViewController: 0x10490a030> whose view is not in the window hierarchy!

原因については、次の記事にあるように、まだ ViewController が存在していないのに、遷移しようとしたため。

対応としては、上記のように viewDidAppear の中で遷移する。

whose view is not in the window hierarchy! 初心者向け エラー

LaunchScreen のガイドライン

Launch Screen – Apple Developer により

重要
起動画面に static 画像を使用しないでください。デバイスの画面サイズと向き」を参照してください。

アプリの最初の画面と同じ起動画面 
アプリの起動が完了したときに外観が異なる要素を含めると、起動画面とアプリの最初の画面の間で不快なフラッシュが発生する可能性があります。また、起動画面がデバイスの現在の外観モードと一致していることを確認してください。

起動画面にテキストを含めない 
起動画面のコンテンツは変更されないため、表示されるテキストはローカライズされません。

高速な起動 
あまり凝ったローンチスクリーンは避けて、コンテンツにすばやくアクセスしてタスクを実行できるようにする。

宣伝しない 
起動画面はブランディングの機会ではありません。スプラッシュ画面や「About」ウィンドウのようなエントリエクスペリエンスを設計しないでください。ロゴやその他のブランド要素は、アプリの最初の画面の固定部分でない限り、含めないでください。ゲームまたは他のイマーシブアプリが最初の画面に移行する前に無地を表示する場合、その無地のみを表示する起動画面を作成できます。

ということなので、高速、シンプル、Autoresizable、宣伝なしの Launch Screen を設計する必要がある。

ScrollView でズームした部分画像

        guard let ciImage = self.imageViewOut.image?.ciImage else { return }
        guard let imageSize = self.imageViewOut.image?.size else { return }
        
        let frameSize = self.scrollView.frame.size
        let ciContext = CIContext(options: nil)

        let zoomScale = self.scrollView.zoomScale
        // ズームしない場合、contentSizeがセットれさず、ゼロのまま。
        // そこで、frameSizeと、zoomScaleから contentSizeを計算する。
        // let contentSize = self.scrollView.contentSize
        let contentSize = CGSize(width: frameSize.width * zoomScale, height: frameSize.height * zoomScale)
        let contentOffset = self.scrollView.contentOffset

        let x = contentOffset.x * imageSize.width / contentSize.width
        let y1 = contentSize.height - contentOffset.y - frameSize.height
        let y2 = imageSize.height / contentSize.height
        let y = y1 * y2
        let newOffset = CGPoint(x: x, y: y)
        
        let newSize = CGSize(width: imageSize.width/zoomScale, height: imageSize.height/zoomScale)
        let newExtent = CGRect(origin: newOffset, size: newSize)
        
        let cgImage = ciContext.createCGImage(ciImage, from: newExtent)
        let uiImageToSave = UIImage(cgImage: cgImage!, scale: 1.0, orientation: .up)
        
        UIImageWriteToSavedPhotosAlbum(uiImageToSave,
                                       self, #selector(didFinishSavingImage), nil)

CIFilter した画像を UIImageWriteToSavedPhotosAlbum で保存できない

CIFilter したUIImageを UIImageWriteToSavedPhotosAlbum で保存できない。CIFilter を適用する前のUIImageは、保存できる。

原因

CIFilterを適用後、CIImageから UIImage を作成したときにプロパティがNULL になっているため。

参考

UIImageWriteToSavedPhotosAlbum() doesn’t save cropped image

CoreImageでエフェクトした画像がUIActivityViewControllerで画像保存できない

対応

    func savePhoto(image: UIImage)
        let ciImage = image?.ciImage
        let ciContext = CIContext(options: nil)
        let cgImage = ciContext.createCGImage(ciImage!, from: ciImage!.extent)
        let uiImageToSave = UIImage(cgImage: cgImage!, scale: 1.0, orientation: .up)
        
        UIImageWriteToSavedPhotosAlbum(uiImageToSave,
                                       self, #selector(didFinishSavingImage), nil)
    }
    
    // エラーの場合アラートを出力
    @objc func didFinishSavingImage(_ image: UIImage, didFinishSavingWithError error: NSError!, contextInfo: UnsafeMutableRawPointer) {
        if error != nil {
            let alertController = UIAlertController(title: "Error", message: "Save Error", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alertController, animated: true, completion: nil)
        }
    }