20200305のSwiftに関する記事は7件です。

Xcode10から11にあげたらBitriseでbuildできなくなった。

この記事の解決方法の対象

  • iOS13対応を終えてBitriseのbuild環境をXcode10からXcode11にあげた
  • Mac Catalystは対応しない
  • xcodebuildコマンドで以下のエラーが出る
xcodebuild: error: Failed to build workspace "workspace_name" with scheme "scheme_name".
Reason: The run destination My Mac is not valid for Archiving the scheme "scheme_name".

原因

Xcode11からの追加機能のMac CatalystのBuild Settingsのフラグを未設定だったためBuild DeviceがMy Macに設定されてしまったことが原因です。

解決方法

Build SettingsでSupport Mac Catalyst = NOを設定をします。
(未設定の場合、検索して開くだけで設定値が入ります)

Build Settings > Mac Catalyst - Deployment > Setting > Support Mac Catalyst
スクリーンショット 2020-03-05 22.28.06.png

あとがき

プロジェクトでは関係ない機能や要素の追加でもBuild Settingsの変更は大きな影響があることがあるので気をつけないといけないなと、久々に時間も使ってしまったこともあり同じトラブルにあった方の助けになれば!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS13.3.1の環境で実機テストするとLibrary not loaded が表示される件について

こんにちは。
先日、外部フレームワークを使ったアプリで実機テストしようとしたら、真っ黒な画面になってしまい、少々ハマってしまったので記事を書きます。
同じことが起きてる人がいたら参考にして欲しいです。

環境

Xcode11.3.1
iOS(実機)13.3.1

解決すべき問題

Firebaseなどの外部ライブラリをCocoaPodsで導入したアプリを実機にインストールすると、「Library not loaded」となって、画面が真っ黒くなるという問題。なお、シュミレーターでは普通に動く。

原因

iOS13.3.1から、Appleは無料のアカウントで外部フレームワークを使用することをブロックしたらしいです、、、
僕は試してないんですが、有料版にアップグレードすると動くらしいです。

解決するためにやったこと

とりあえずエラーをコピペして検索してみた。そしたら、上の原因に書いたことが影響しているってことは分かったのですが、じゃあ1万円払って有料のデベロッパーアカウントを作成するのもなんか悔しい。

それで色々調べた結果、無料アカウントでも外部フレームワーク使える方法がありました。
それは、Podfileのuse_frameworks!をuse_modular_headers!に変更するという方法です。

こんな感じに、、、

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'AppName' do
  # Comment the next line if you don't want to use dynamic frameworks
  # use_frameworks!
  use_modular_headers!

  # Pods for AppName

  pod 'Firebase'
  pod 'Firebase/Analytics'

end

ここでは、use_frameworks!をコメントアウトしています。

もし、せっかく作ったアプリが動かなくて困っている方がいたら参考にしていただければと思います。

参考にしたサイト

https://stackoverflow.com/questions/60096258/library-not-loaded-rpath-fblpromises-framework-fblpromises-ios-13-3-1

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[swift]カスタムメニュー、ハンバーガーメニューで使いたい表示方法 備忘録

これを作ります。

スクリーンショット 2020-03-05 18.13.49.png
スクリーンショット 2020-03-05 18.13.59.png

作り方

まずは右に新しいviewを貼りましょう。
そこからは画像の通りに、、

スクリーンショット 2020-03-05 18.19.57.png

スクリーンショット 2020-03-05 18.03.51.png
スクリーンショット 2020-03-05 18.11.56.png

メイン画面とメニュー画面を接続して、、
スクリーンショット 2020-03-05 18.07.09.png

おしまい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] コードで書くUIView、 ボタンを押すと画面遷移 備忘録

override func viewDidLoad() {
  super.viewDidLoad()
  Button()
}

func Button() -> UIView?{
  let button = UIButton(frame: CGRect(x:self.view.frame.maxX - 50, y:0, width:50, height: 50))
  button.backgroundColor = UIColor.black
  button.setTitle("追加", for: .normal)
  //追加ボタンがタップされた際に画面遷移をする(buttonTappedはタップされた際に呼び出される関数)
  button.addTarget(self, action: #selector(ViewController.buttonTapped(sender:)), for: .touchUpInside)
  view.addSubview(button)
  return view
}

@objc func buttonTapped(sender:UIButton){
  let storyboard: UIStoryboard = self.storyboard!
  let second = storyboard.instantiateViewController(withIdentifier: "secondView")
  self.present(second, animated: true, completion: nil)
}

StoryboardIDを記入
スクリーンショット 2020-03-05 17.49.55.png

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Navigation Controllerをコードで実装する

Storyboardでいうところの、以下のようなNavigation Controllerをコードで実装する方法をご紹介します

スクリーンショット 2020-03-05 10.09.57.png

まず、UINavigationControllerを継承したクラスを作成します

NavigationController.swift
class NavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

AppDelegateに以下のように記述します

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let nav = NavigationController()
        let vc = ViewController()
        nav.viewControllers = [vc]

        //NavigationControllerをrootViewに設定する
        window?.rootViewController = nav
        window?.makeKeyAndVisible()

        return true
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIのViewは自分のサイズを自分で決めます

もと(英語)→
https://netsplit.com/swiftui/views-choose-their-own-sizes/

外国人なので日本語がおかしいところがあれば是非教えてください。

SwiftUIの紹介記事などでだいたい最初に書かれるのは「ビューは自分で自分のサイズを決め、一回決められたサイズは上書きできません」とのことですが、こんな簡単そうなことでも以外と複雑な闇を隠しています。なぜならその簡単そうな宣言が我らの知っているレイアウトについての全ての常識を変えるのです。

SwiftUIはUIKitやAppKitの考え方とは違いますしメリットとデメリットもありますので時間をかけてちゃんと勉強する価値はあると思います。なのでこの記事ではたくさんのレイアウトの土台になるビューの2つ(ImageとText)を見ながら宣言の深い意味を考えてみましょう。

Images

まず画像を表示しましょう:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
    }
}

ソース画像のサイズ(解像度)は 101px x 92px なので表示される画像も同じサイズになって、画面の真ん中に表示されます。

frame.png

見やすいため記事のスクショは全部ボーダー付きにしました。
画像のボーダーは全部「赤色」
デバイスのボーダーは「グレー」
フレームのボーダーは「緑色」
テキストのボーダーは「黄色」
他の記事でSwiftUIのボーダーについて書きますがとりあえず今回はビューのバウンドを見やすくするために使用し、コードからは抜きます。

.framemodifierを使うと画像をフレームの中に入れることができます。

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .frame(width: 200, height: 200)
    }
}

frame2.png

UIKit, AppKitなどだとこれは意外な結果ですよね?

画像を拡大するのじゃなくて、画像は同じサイズそのままフレームの真ん中にドンと置いてあるだけです。

結局のところ、.frameは使用されたビューのプロパティーを変更せず指定されたサイズで新しく "frame" というビューを作成し使用されたビューがその新しいフレームのチャイルドとして追加されます。

ポイント!!.frameは使用されたビューのプロパティーを変更せず指定されたサイズで新しく "frame" というビューを作成し使用されたビューがその新しいフレームのチャイルドとして追加されます。

これがSwiftUIの基礎的なところなのでちゃんと理解をした方がいいんじゃないかなと思います。 .frameみたいなmodifierは新しくビューを作成し元のビューが新しいビューにチャイルドとして追加されます。

Imageのサイズが画像アセットだけを元に選ばれます。親ビューがそれを上書きできません。

シンプルなデモとして小さすぎるフレームに画像をぶち込んでみましょう。

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .frame(width: 50, height: 200)
    }
}

多分これだと画像が必ずリサイズされる!かと思うのは普通ですが、SwiftUIだと画像がフレームをオーバーフローするだけです。

overflow.png

重ねられるビューを作る時、これは使えますね。
ですが、.clipped.cornerRadiusみたいな、画像を切り取るmodifierを使うとフレームのバウンドを元に画像を切り取ることは可能です。が、長くなるのでこの記事では詳しくは書かないことにします。

あとでまた画像に戻りますがここで他に確認するべきところがありますので一旦Textをみてみましょう。

Text

さっきのレイアウトを画像じゃなくてキャラクターの名前の文字列にしてみましょう。

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
    }
}

text_frame.png

画像と同じように画面の真ん中に文章が表示されています。Textも文字がちゃんと入るサイズを自分で決めました。そして.frameも同じように使うことはできます。

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .frame(width: 200, height: 200)
    }
}

.frameがもう1つの新しいビューを作ることを知っているとTextが前と同じく真ん中に置いてあることに驚きはないと思います。

text_frame_2.png

Textはサイズ選びに設定は色々ありますよ。例えば.fontでフォントを選べます。ヒーローの名前なので.titleにしましょう。

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
    }
}

フォントが大きくなったので前よりテキストが大きくなっています。

title_text.png

画像の対応と完全に同じにするため大きくなったTextを小さすぎるフレームにぶち込んでみます。

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
            .frame(width: 200, height: 200)
    }
}

似たコードで画像のサイズを全く変更せずにフレームをオーバーフローしてしまったが、Textだと.fontでサイズの変更できましたしこれの結果は違うのかな?!

title_text_multiline.png

オーバーフローせず、ちゃんとフィットするサイズを選ぶことはできたらしい。この場合だとコンテンツを2行に分け、横幅の広さを抑えて縦幅を大きくしました。

これは「親ビューは決まったサイズを上書きできない」とのことを矛盾しない。ただまだ勉強していないもう1つの原則を表す→ ビューが自分のサイズを決める前に親から「提案」をもらいます。

ポイント!!ビューが自分のサイズを決める前に親から「提案」をもらいます。

ちなみに「親が子に渡す提案サイズ」と「親が決める自分(親)のサイズ」は別物です。大体、親は子ビューを元に自分のサイズを選びますので。

Flexible Framesは複雑なので、他の記事でもう少し詳しく書きます。

提案サイズについて

前の例で、サイズを決めてビューが設置された手順の理解がSwiftUIの理解の鍵になります。Textが自分のサイズを決めて、そのサイズを上書き不可能。だがTextは親から渡されたサイズをもとに自分のサイズを柔軟に選べます。

Frameを200 x 200のサイズを指定したのでTextを設置する準備が終えた後にTextに提案サイズとして自分のサイズを渡し、その後フレームが、チャイルドになっているTextに自分のサイズを決めろと頼みます。

Frameがない例でデバイスのバウンドがTextに提案サイズを渡しました。

Frameがある例で提案された横幅(width)が足りなかったが縦幅(height)に十分のスペースがあったのでコンテンツを2行に分けたら収まることができます。

Textは親が渡した提案サイズを元に自分のコンテンツの最適な表示方法を決めてそのように表示してるのです!

普段は決められたサイズが提案サイズより小さいのがいいです、コンテンツが丁度入るぐらいの大きさ。んで、親から渡された提案サイズが大ぎるとしても自分がそのサイズに合わせて大きくなること基本的にないです。

それに、上の例のように提案サイズのwidthをそのまま選ぶじゃなくて、2行になった文字列が入るの、提案より小さいサイズになりました。Frameよりは小さいのでTextをFrameの真ん中に設置されることになりました。

ImageがFrameより大きい場合のようにTextがFrameより縦が大きい時も同じようなことが起きます。

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .frame(width: 200, height: 10)
    }
}

ImageがFrameをオーバーフローするしTextもオーバーフローしちゃいます。

text_frame_overflow.png

テキストの切り捨て

サイズを柔軟に選ぶのに数行に分けるのはTextのたった1つの選択ではありません。文字と文字の間のスペースを小さくすることもでき、コンテンツを切り捨てることもできます。こうやって切り捨て機能を確認できます →

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
            .lineLimit(1)
            .frame(width: 200, height: 200)
    }
}

.lineLimitを使ったことによって前の無限行数ではなく1行に収まるように命令しましたので、提案サイズが足りないときはTextが違うやり方でコンテンツを抑えてサイズを選ばないといけない。1行で文字列の横幅をフィットすることができず、数行使うこともできないのでTextが文字列を切り捨てて自分の横幅を減らすようにしてます。

title_text_frame.png

もう一度、選ばれた横幅はコンテンツが丁度入るくらいのサイズで親ビュー(Frame)の真ん中にポンと置かれています。

縦幅が足りない時でも切り捨てが起きることがあります。

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
            .frame(width: 200, height: 50)
    }
}

行数が限られてないとしても提案サイズの縦幅が1行しか入らないのでこの例でコンテンツが提案サイズに合わせてます。

text_frame_truncation.png

リサイズ可能な画像

ビューが自分のサイズを選ぶときに柔軟に選べることはわかった上でもう一度Imageを見てみましょう。

普段Imageは画像ファイルの解像度のサイズになりますが、リサイズすることもできます。

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .resizable()
            .frame(width: 200, height: 150)
    }
}

リサイズ可能なImage(リサイザブル)は親ビューからもらった提案サイズそのまま選んでできるだけスペースを埋むようにします。

resizable_image.png

デフォルトでImageは画像を伸ばしてスペースを埋むけど.resizable(resizingMode: .tile)を使うと画像をリピートすることができます。

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .resizable(resizingMode: .tile)
            .frame(width: 200, height: 150)
    }
}

ImageのサイズはまだFrameからもらった提案サイズのままなのですが、単に画像を複数回描画しているだけに注意。

tile_image.png

アプリつくっているとき、画像を伸ばすのも複数描画のも違う単純に「変に伸ばさずこのサイズで一番大きいサイズで画像を表示したい」だけの時が多いと思いますが、その時は.aspectRatioという便利なmodifierもあります。.aspectRatio.resizableと一緒に使うことでアスペクト比変わらずリサイズをすることが可能になります。

Modifierの順番

複数なmodifierを使うときは順番は重要です。各modifierがそのmodifierが使われたビューに適応されるのでSwiftUIのmodifierは逆の順番に書かれるのです。

ポイント!!SwiftUIのmodifierは逆の順番に書かれるのです。

ソース画像のアスペクト比で正しくframeのサイズに合わせたImageを作るのに以下の順番でmodifierを書く必要があります。

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 200, height: 150)
    }
}

このコードでは.resizableはImageに追加されました。ってことはImageは.resizableのチャイルドになります。.frameと同じように。

そのあとは第2のmodifier、.aspectRatioを追加することによってaspectRatioは「リサイザブル画像」に適応されて、「リサイザブル画像にアスペクト比を適応する」という流れなので、modifierチェーンの最後に追加します。

そして最後に「アスペクト比を使うリサイザブル画像」をframeに打ち込みたいので最後に.frameを追加。大事なポイントはチェーンの最後にframeは来てますがビューの構成ではframeは一番最初のビューになります。

そしてそれで正しく表示できます→

aspectratio_image.png

ソース画像と親ビューの提案サイズを参考にして、Imageが親が渡したheightを使おうとしましたが、元画像のheightが足りないので元画像のサイズを大きくしてheightを選び、アスペクト比が変わらないためのwidthを計算して選んで、サイズを決定しました。

親の横幅が大きすぎて子ビューが全部使わないので真ん中に置くことになりました。

こういったルールを理解した上で複数ビューをstackで合体することやflexible frameの奇妙なケースなどを確認できるでしょう!

Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Firebaseのuser.photoURLをカメラロールの画像をアップして設定する

カメラロール(ローカルファイル)から写真をピックし、ローカルのURLを指定してStorageにアップロード。その際のDownloadURLをuser.photoURLに指定してやる。

import UIKit
import Firebase
import FirebaseDatabase
import FirebaseStorage

class EditProfileViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {    
    var user: User!
    var ref: DatabaseReference! //
    private var databaseHandle: DatabaseHandle! //
    var storage: Storage!
    var storageRef: StorageReference!
    var localImageURL: NSURL?

@IBAction func uploadImageButtonDidTap(_ sender: Any) {
        let ipc = UIImagePickerController()
        ipc.delegate = self
        self.present(ipc, animated:true, completion:nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true, completion: nil);
        self.profileImageView.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
        self.localImageURL = info[UIImagePickerController.InfoKey.imageURL] as? NSURL
    }


@IBAction func saveDidTap(_ sender: Any) {
        let changeRequest = user.createProfileChangeRequest()
        guard let imageURL = localImageURL else { return }
        let localFile = imageURL as URL // ここは公式ドキュメントに揃えただけなので別に変数化しなくていい
        let imageRef = storageRef.child("user/\(user.uid)/images/profile.jpg")
        _ = imageRef.putFile(from: localFile, metadata: nil) { metadata, error in
            guard let metadata = metadata else {
                // Uh-oh, an error occurred!
                return
            }
            // Metadata contains file metadata such as size, content-type.
            let size = metadata.size
            // You can also access to download URL after upload.
            imageRef.downloadURL { (url, error) in
                guard let downloadURL = url else {
                    // Uh-oh, an error occurred!
                    return
                }
                changeRequest.photoURL = downloadURL
                changeRequest.commitChanges() { (error) in
                    self.dismiss(animated: true, completion: nil)
                }
            }
        }

    }
}

https://firebase.google.com/docs/storage/ios/create-reference?hl=ja
https://firebase.google.com/docs/auth/ios/manage-users?hl=ja#update_a_users_profile
https://stackoverflow.com/questions/40177250/firebase-how-to-get-image-url-from-firebase-storage

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む