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

[Swift 5.1] Swift Package ManagerのテストをLinux上でも実行する

Tl;Dr

Swift 5.1 から追加された--enable-test-discoveryオプションを渡せばよい。

$ swift test --enable-test-discovery

ただし、正しく検出できないバグも残っているらしいので、心配ならば Swift 4.1 から追加された--generate-linuxmainを利用してテスト実行用のソースを自動生成できる。

$ swift test --generate-linuxmain # 自動生成
$ swift test # 実行

つまり、Swift 4.1 以降であればテスト一覧を自分で列挙する必要はない

Linux環境で実行すべきテストを列挙する(Swift 5.1未満)

Swift 5.1 においてswift package initでプロジェクトを初期化すると、Testsディレクトリ配下が以下のように生成される(生成時のルートディレクトリはSwiftPackageExample)。

$ tree Tests 
Tests
├── LinuxMain.swift
└── SwiftPackageExampleTests
    ├── SwiftPackageExampleTests.swift
    └── XCTestManifests.swift

このうちLinuxMain.swiftXCTestManifests.swiftは、Linux などの macOS 以外の OS でテストを実行するためのファイルであり、前者が『エントリポイント』、後者が『実行すべきテストの一覧を列挙』するためのものになっている。

これらが必要なのはmacOS以外の環境では実行すべきテストの一覧を自動的に検出できなかったことにある。

それぞれのソースを簡単に見てみる。

LinuxMain.swift

LinuxMain.swiftでは、後者のXCTestManifests.swiftに定義されたメソッドを呼び出すことで、実行すべきテスト一覧を取得し、XCTMain()関数でテストを実行している。

LinuxMain.swift
import XCTest

import SwiftPackageExampleTests

var tests = [XCTestCaseEntry]()
tests += SwiftPackageExampleTests.__allTests() // XCTestManifests.swiftに定義されたメソッドを呼び出し

XCTMain(tests)

XCTestManifests.swift

XCTestManifests.swiftでは、①extensionを利用して各テストクラスの『テスト名』と『テストメソッドの参照』のタプルの一覧を返すように定義され、②さらに前述のLinuxMain.swiftから呼び出される__allTests()関数においてtestCase()関数でXCTestCaseEntryに変換して実行すべきテストの一覧を返すようになっている。

XCTestManifests.swift
#if !canImport(ObjectiveC)
import XCTest

// ①
extension SwiftPackageExampleTests {
    // DO NOT MODIFY: This is autogenerated, use:
    //   `swift test --generate-linuxmain`
    // to regenerate.
    static let __allTests__SwiftPackageExampleTests = [
        ("testExample", testExample),
    ]
}

// ②
public func __allTests() -> [XCTestCaseEntry] {
    return [
        testCase(SwiftPackageExampleTests.__allTests__SwiftPackageExampleTests),
    ]
}
#endif

--generate-linuxmainで自動生成する(Swift 4.1+)

Swift 4.1未満ではXCTestManifests.swiftを自分でメンテする必要があり、テストメソッドを新たに追加するごとにテスト一覧の定義を更新する必要があった。

それが Swift 4.1 において--generate-linuxmainというオプションが追加され、完全に自動生成することが可能になった。

$ swift test --help
...
  --generate-linuxmain    Generate LinuxMain.swift entries for the package

以下のコマンドでLinuxMain.swiftXCTManifests.swiftが自動生成される。

$ swift test --generate-linuxmain

前述のXCTestManifests.swiftのコメントをみるとDO NOT MODIFY: This is autogeneratedと記述されており、自動生成されていることがコメントからも読み取れる。

// DO NOT MODIFY: This is autogenerated, use:
//   `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__SwiftPackageExampleTests = [

--enable-test-discoveryで自動検出する(Swift 5.1+)

Swift 5.1 では--enable-test-discoveryという macOS 以外において、テストの一覧を自動的に検出してテストを実行するオプションが追加された。

$ swift test --help
...
  --enable-test-discovery
                          Enable test discovery on platforms without Objective-C runtime

これを利用すれば前述したLinuxMain.swiftXCTestManifests.swiftは不要となり、Linux環境でも以下のコマンドで実行できるようになる。

$ swift test --enable-test-discovery

しかし、このオプションにはまだバグが残っているらしい(執筆時点)。

自身のプロダクトで正しく検出できるのかは、--list-testsオプションでテスト一覧を列挙し、それらの件数が一致するかを確認するとよい。

# on mac
$ swift test --list-tests 
SwiftPackageExampleTests.SwiftPackageExampleTests/testExample

# on Linux
$ swift test --enable-test-discovery --list-tests
SwiftPackageExampleTests.SwiftPackageExampleTests/testExample

もっとも、これはこの時点において正しく検出できるかの確認に過ぎないので、自身のプロダクトにおいてLinux環境でのテストが重要なものであれば、このオプションがデフォルトになる(つまり指定が不要になる)まで見送ったほうがよいかもしれない。

Dockerで実行できるようにする

mac のローカル環境でもDockerで実行できるようにしておくと何かと便利である。

以下はSwift 5.1のコンテナで実行するMakefileの例である。

Makefile
linux-test:
    docker run --rm \
        --volume "$(CURDIR):/src" \
        --workdir "/src" \
        swift:5.1 \
        swift test --enable-test-discovery

これは次のように実行できる。

$ make linux-test

ついでにコンテナにログインするターゲットを用意しておいてもよいかもしれない。

Makefile
linux:
    docker run --rm -it \
        --volume "$(CURDIR):/src" \
        --workdir "/src" \
        swift:5.1

--volumeでマウントしているので、ソースの変更などはすべてmac側で行える。

$ make linux
docker run --rm -it \
                --volume "/Users/hosonumayuusuke/go/src/github.com/YusukeHosonuma/SwiftPrettyPrint:/src" \
                --workdir "/src" \
                swift:5.1
root@ff97680b040b:/src# 

GitHub Actionsで実行できるようにする

以下のようにruns-onubuntu-latestcontainerswift:5.1を指定してジョブを定義すればよい。

name: Test

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  unit-test-in-linux:
    runs-on: ubuntu-latest
    container: swift:5.1

    steps:
    - uses: actions/checkout@v2

    - name: Run Unit Test
      run: swift test --enable-test-discovery

参考PR

自身のOSSプロダクトについて、以下のPRで対応したので参考までに。
https://github.com/YusukeHosonuma/SwiftParamTest/pull/34

LinuxMain.swiftを残してしまっているが、これは本来であれば不要なのでご留意を。

参考記事

終わりに

Swift Package Manager は Swift と共に進化してきたこともあり、古いバージョンについて言及された記事も多数残っているため、参考情報として利用する際には注意が必要なように思う。

かくいう私も Swift 4.1 の時点ですでに追加されていた自動生成用の--generate-linuxmainの存在を知らず、 30個以上もあるテストの定義を手書きしてしまうところであった (というより、この記事に書いたLinux環境でのテストについて知ったのも最近のことだ)。

今であれば公式のパーサである swift-syntax もあるので、自動生成用のツールを書いてしまうかと思い、ふとすでに存在しないかとググってみたら参考記事に貼ったような情報を得られたという次第であった。

日本語圏においては Swift Package Manager の情報は英語圏に比べてさらに少なく、私自身も『LinuxMain.swiftとは』でググっても殆ど出てこない状況に遭遇したことを思い出し、筆を執ることにした。

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

Swift Network.framework Study 20200319「Receive and Send」

Study

Network.framework
Study:Receive and Send

環境

Client:Java、NetBeans
Server:Swift、Xcode

Server Source Swift

main.swift

import Foundation
import Network

var receiveANdSend = ReceiveAndSend()
receiveANdSend.startListener()
while receiveANdSend.running {
    sleep(1)
}

ReceiveAndSend.swift

import Foundation
import Network

class ReceiveAndSend {
    public var running = true

    func startListener() {
        let myQueue = DispatchQueue(label: "ExampleNetwork")

        do {
            let nWListener = try NWListener(using: .tcp, on: 7777)
            nWListener.newConnectionHandler = { (newConnection) in
                print("New Connection!!")
                newConnection.start(queue: myQueue)
                self.receive(nWConnection: newConnection)
            }
            nWListener.start(queue: myQueue)
            print("start")
        }
        catch {
            print(error)
        }
    }

    func receive(nWConnection:NWConnection) {
        nWConnection.receive(minimumIncompleteLength: 1, maximumLength: 5, completion: { (data, context, flag, error) in
            print("receiveMessage")
            if let data = data {
                let receiveData = [UInt8](data)
                print(receiveData)
                print(flag)
                self.sendMessage(nWConnection)
                if(flag == false) {
                    self.receive(nWConnection: nWConnection)
                }
            }
            else {
                print("receiveMessage data nil")
            }
        })
    }

    func sendMessage(_ connection: NWConnection) {
        let data = "Answer".data(using: .utf8)
        let completion = NWConnection.SendCompletion.contentProcessed { (error: NWError?) in
            print("応答送信完了")
            self.running = false
        }
        connection.send(content: data, completion: completion)
    }
}

Client Source Java

public class ExampleSendAndReceive {
   public static void main(String[] args) {
        try(Socket socket = new Socket("localhost", 7777);
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);) {

            Runnable runnable = () -> {
                System.out.println("Receive start");
                try {
                    int data = socket.getInputStream().read();
                    while(data != -1) {
                        System.out.print(data + " ");
                        data = socket.getInputStream().read();
                    }
                    System.out.println("end");
                }
                catch(Exception e) {
                    System.out.println(e);
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();

            for(int i = 0; i < 5; i++) {
                printWriter.println("12345");
                Thread.sleep(5000);
            }
        }
        catch(Exception e) {
            System.out.println(e);
        }
    }    
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【随時更新】iOS開発で使ったライブラリーまとめ(20個)

他の方もまとめてくれているものが沢山あります。あくまで自身のメモ代わりで。
とりあず、開発序盤は、ライブラリ漁りしてる事が多いです。
毎回探すのが面倒なので、まとめてメモしておくことにします。
※基本的には自分が開発で実際に導入したもののみ記載(一部違う)。
※AWSやFirebaseは除外。

サマリ

通信系ライブラリ

Alamofire

いわずもがな、超メジャー通信ライブラリ
説明不要かな

Moya

Alamofireをさらに拡張して、使いやすくしたライブラリ
Alamofireに慣れているとそれほどかもしれませんが、
ちょっとした所で使いやすくなっています。
スクリーンショット 2020-03-12 12.30.22.png

DB関連

MagicalRecord

CoreDataを簡単に利用するためのライブラリ
こちらも老舗のメジャーライブラリ

Realm

モバイルデータベースのRealmです。
デファクトスタンダードと言っても過言ではないと思ってます。

パース系

最近はほとんどがJSON利用しているので、XMLとかライブラリあると地味に助かってます。

SwiftyJson

Jsonパース系では、追随を許さないくらいのメジャーライブラリ

SWXMLHash

こちらはXMLをパースしてくれます。
XMLは最近使うことがあまりないのですが、たまにでてくると非常に面倒なので助かります。

SwiftyXMLParser

Yahoo Japanさんが提供してくれているライブラリ
SwiftyJsonやSWXMLHashをインスパイヤされて作成さたようです。
使い方の好み的には、SWXMLHashよりこっちかなー。

UI系

ローディング関係は、好みが分かれたり、アプリのコンセプトなどにもよるので
スタンダード(地味?)なものが好まれている印象です。

SVProgressHUD

こちらも有名なローディングライブラリ
試してみたら、iOS13だと位置がずれた。。。

※画像はGithub引用

SwiftSpiner


※画像はGithub引用

PKHUD

Swift製で派手な要素はないが、シンプルで使いやすくなってます。

※画像はGithub引用

CDAlertView

アラート(ダイアログ)表示ライブラリ
表示だけでなく、アニメーションも可能、使い方もシンプルなので便利です。

※画像はGithub引用

MessageKit

LineのようなチャットメッセージUIを提供してくれます。
導入方法も簡単、テキスト、画像、MAPなどにも対応しています。

※画像はGithub引用

IQKeyboardManagerSwift

こちらは、入力時のキーボードが表示された際に画面を自動であげてくるライブラリです。
導入含めてめちゃくちゃ助かってます。

※画像はGithub引用

TextFieldEffects

テキスト入力フィールドをスタイリッシュにしてくれます。
いくつかスタイルも用意されていて、使いやすいです。

SwiftyPickerPopover


※画像はGithub引用

HideShowPasswordTextField

TexFieldの文字を表示したり、マスクしたりできる便利

※画像はGithub引用

拡張、ユーティリティ、その他系

R.swift

Androidでおなじみのリソース関連を簡単に取得できる仕組みですが、
それのSwift版ですね。
Storyboad、Segue、xib、image、などがコード補完で型までちゃんとして取得できる。

KeychainAccess

Keychainをラッピングして使いやすくしてくれています。
共通クラスに記述して使用してました。

PromiseKit

非同期処理用のライブラリ
シンプルに非同期処理が記述できるのがうり。
まぁ、Javascriptで慣れているかたも多いのではないでしょうか。

Promises

Google製のライブラリ
PromiseKitと同様にシンプルに非同期処理が記述できる
Github見るかぎり、こっちのほうが早いよって書いてある。
※これは未使用

まとめ

一回使って便利だったものは、CocoaPods、Carthageと管理は変われど、概ねリピートしてます。
その他、もっと使ってるのですが暇を見つけて追加していきます。
他にもコード拡張などもあるので、それもいつか記事にしたい。

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

UnityでiOS向けにビルドしたアプリにおいて、音声をバックグラウンド再生する備忘録(20200319)

目的

UnityでiOS向けにビルドしたアプリにおいて
アプリがバックグラウンドになっても音声を再生しつづけたい

ステップ

  • Unityスクリプト内 OnApplicationPause でバックグラウンド時に音声を再生する関数を実行
  • iOS用にunityからビルドし、 書き出されたxcode pjファイルを開く
  • Signing & capabilitiesでbackground modesを追加。Audio,Airplay~にチェックを入れる

  • UnityAppController.mmのstartUnity関数内ににaudioSessionを定義している箇所があるので、下記記述を追加する

[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

この記事を書いた理由

下記記事の内容を参考にしたが、
スレッドが長い上に古い情報が混ざっていて解決に時間がかかったため。
https://forum.unity.com/threads/how-do-i-get-the-audio-running-in-background-ios.319602/

備考 swiftで書くとき

extension AppDelegate {
    //background play(audio)
    func prepareBackgrounPlay(){
        let audioSession = AVAudioSession.sharedInstance()

        //background audio play
        do {
            try audioSession.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: [.mixWithOthers, .allowAirPlay])
            print("Playback OK")
            try audioSession.setActive(true)
            print("Session is Active")
        } catch {
            print(error)
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iCloud Documentsを使ってファイルの読み書きをする

まえがき

iCloudに置いたファイルをユーザー/アプリ間で共有し、どちらからでも自由にアクセスできる機能を実装します。(Keynoteとかがそうですね)

iCloud Documents Storageを使うと上手くいきそうな事はすぐ分かったのですが、実際に動くところまで持っていくのに結構苦労しました。そこで得られた知見を共有したいと思います。

公式ドキュメントはこちらです。

開発環境

macOS 10.15.3
Xcode 11.3.1
iPad OS 13.3.1

Apple Developer Programに登録したアカウントが必要です。

下準備

実装を始める前に、環境構築します。

プロジェクトの作成

Xcodeを立ち上げて、ツールバーからFile → New → Project... を選んでください。
Single View Appを選択し、 好きなProduct Nameを入力します。

1.png

Capabilityの追加

iCloudの機能を使うには、Capabilityを追加する必要があります。
赤丸で囲ったボタンを押して、iCloudを追加してください。
c.png

その後、iCloud Documents にチェックを入れます。

コンテナの追加

iCloudのCapabilityを追加したからと言って、iCloud内の全てにアクセスはできません。コンテナ(フォルダみたいなもの)を指定し、そのコンテナ内のみアクセスできます。

+ボタンを押して新しくコンテナを作成します。コンテナ名は好きなものを入れてください。こだわりが無ければProduct Nameと同じでいいと思います。

公式ドキュメントにある通り、コンテナは一度作ると削除することができません。typoしてないかよく確認してください。テスト用に適当に作ったものももちろん削除できないので注意してください。
d.png

コンテナ作成後、文字が赤くなっている場合は更新ボタンを押してください。

vv.png

Info.plistの編集

Info.plistを右クリックし、 Open As → Source Codeを選びます。そして、以下のコードを追加してください。

<key>NSUbiquitousContainers</key>
<dict>
    <key>iCloud.kakeru.iCloudDocumentTest</key> ← ここにコンテナ名を入れます
    <dict>
        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
        <true/>
        <key>NSUbiquitousContainerName</key>
        <string>iCloudDocumentTest</string>
        <key>NSUbiquitousContainerSupportedFolderLevels</key>
        <string>Any</string>
    </dict>
</dict>

Property List形式だと以下のような形になります。
df.png

  • NSUbiquitousContainerIsDocumentScopePublic
    • trueにするとコンテナのDocumentsフォルダ内が、iCloud Drive上にフォルダとして見えるようになります。
  • NSUbiquitousContainerName
    • iCloud Drive上で表示するフォルダ名です。
  • NSUbiquitousContainerSupportedFolderLevels
    • None: Documentsフォルダ内には、フォルダを作ることができません。
    • One: Documentsフォルダ直下であれば、フォルダを作れます。
    • Any: 制限なし

各キーの詳しい情報はこちらの公式ドキュメントを確認してください。

実装

test.txtというファイルの読み書きを実装します。コードが長くなるため、エラーハンドリングは省略しています。各自で追加してください。

まず、UIDocumentを継承したクラスを作成します。このクラスは適当にググって出てきたものをベースに作ったため、もっとスマートな書き方があるかもしれません。

class Document: UIDocument {
    var text: String? = ""

    override func contents(forType typeName: String) throws -> Any {
        text?.data(using: .utf8) ?? Data()
    }

    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        guard let contents = contents as? Data else { return }
        text = String(data: contents, encoding: .utf8)
    }
}

ファイルの新規作成

以下のコードだけで、test.txtファイルがiCloud上に作成されます。かんたんですね。

let url = FileManager.default.url(forUbiquityContainerIdentifier: nil)!
    .appendingPathComponent("Documents")
    .appendingPathComponent("test.txt")

let document = Document(fileURL: url)
document.save(to: url, for: .forCreating)

ファイルへの書き込み

新規作成とあまり変わりません。

let url = FileManager.default.url(forUbiquityContainerIdentifier: nil)!
    .appendingPathComponent("Documents")
    .appendingPathComponent("test.txt")

let document = Document(fileURL: url)
document.text = document.text! + "追記"
document.save(to: url, for: .forOverwriting)

ファイルの読み込み

以下を実行すると、test.txtファイルの検索が始まります。ファイルが見つかれば中身を出力します。これも適当にググって出てきたものがベースなので、もっとスマートな書き方があるかもしれません。(特にNSMetadataQuery)

let metadata = NSMetadataQuery()
metadata.predicate = NSPredicate(format: "%K like 'test.txt'", NSMetadataItemFSNameKey)
metadata.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]

NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: metadata, queue: nil) { notification in
    let query = notification.object as! NSMetadataQuery
    if query.resultCount == 0 { return }

    let url = (query.results[0] as AnyObject).value(forAttribute: NSMetadataItemURLKey) as! URL
    let document = Document(fileURL: url)
    document.open { success in
        if success {
            print(document.text)
        }
    }
}

metadata.start()

完全に動くコード

みなさんが欲しいのはこれですよね。

ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var text: String = ""
    private let containerManager = ContainerManager()

    var body: some View {
        VStack() {
            TextField("テキストを入力...", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(width: 200)

            Button("Save") {
                self.containerManager.save(self.text)
            }
            Button("Load") {
                self.containerManager.load {
                    self.text = $0 ?? ""
                }
            }
            Button("Clear") {
                self.text = ""
            }
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

class ContainerManager {
    private var metadata: NSMetadataQuery! // 参照を保持するため、メンバとして持っておく。load()内のローカル変数にするとうまく動かない。
    private var url: URL {
        FileManager.default.url(forUbiquityContainerIdentifier: nil)!
            .appendingPathComponent("Documents")
            .appendingPathComponent("test.txt")
    }

    func load(completion: @escaping (String?) -> Void) {
        metadata = NSMetadataQuery()
        metadata.predicate = NSPredicate(format: "%K like 'test.txt'", NSMetadataItemFSNameKey)
        metadata.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]

        NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: metadata, queue: nil) { notification in
            let query = notification.object as! NSMetadataQuery

            if query.resultCount == 0 {
                print("ファイルが見つからなかったので新規作成")
                let document = Document(fileURL: self.url)
                document.save(to: self.url, for: .forCreating) { success in
                    print(success ? "作成成功" : "作成失敗")
                    completion(nil)
                }
                return
            }

            let url = (query.results[0] as AnyObject).value(forAttribute: NSMetadataItemURLKey) as! URL
            let document = Document(fileURL: url)
            document.open { success in
                if success {
                    print("ファイル読み込み: \(document.text ?? "nil")")
                    completion(document.text)
                } else {
                    print("ファイル読み込み失敗")
                    completion(nil)
                }
            }
        }

        metadata.start()
    }

    func save(_ text: String) {
        let document = Document(fileURL: url)
        document.text = text
        document.save(to: url, for: .forOverwriting) { success in
            print("ファイル保存\(success ? "成功" : "失敗")")
        }
    }
}

class Document: UIDocument {
    var text: String? = ""

    override func contents(forType typeName: String) throws -> Any {
        text?.data(using: .utf8) ?? Data()
    }

    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        guard let contents = contents as? Data else { return }
        text = String(data: contents, encoding: .utf8)
    }
}

ハマったところ

Info.plistを編集しても、うまく反映されない事が多々あります。例えば、 NSUbiquitousContainerIsDocumentScopePublic の値をtrue → falseに変えても、iCloud Drive上でフォルダが見えたままとか。
公式のQ&Aにもありますが、仕様のようです。

Info.plistを編集を編集した場合は、以下おまじないをすると反映されるかと思います。

  1. Bundle Identifierを変更する。 Version, Buildの数字を増やす。スクリーンショット 2020-03-18 17.55.13.png
  2. Xcodeを再起動する
  3. Info.plistの編集が元に戻っていないか確認する(してることがあります!)
  4. Xcodeのツールバー → Product → Clean Build Folder
  5. Product → Run
  6. 変更が反映されたのを確認したら、Bundle Identifier, Version, Buildを元に戻す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Storyboardの名前を変えようとしたら思った以上に変えるべき場所が多かった話

環境

Xcode 11.3.1でSceneDelegateも含めた新規プロジェクトを作成した。
対応デバイスはiPadのみにしていた。

何が起きたか?

アプリを製作中、ストーリーボード分割のためにMain.storyboardからViewController.storyboardに名前を変えて以下の部分を設定

image.png

これで大丈夫だと実行したところこのエラーが出た

Could not find a storyboard named 'Main' in bundle NSBundle

解決法

iOS13にはiPad対応にするとStoryboardの名前を指定するところがInfo.plistに三ヶ所ありそのすべてを新しい名前にしないといけなかった。

image.png

この画像のViewControllerと書かれているところに新しいStoryboardの名前を入れておかなければいけない。
特にこのネストの深いところにある部分が本当に気づけない。

ちなみにGUI上のMain Interfaceと同期したのはMain storyboard file name (iPad)のみだった。

感想

いや、知らんがな笑

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

x-codeでiOSアプリを横画面対応させる

主旨

iOSの実機を横向きにしたときに画面表示をその向きに対応させるための設定方法。
3つ設定する場所があります。

1. General > Device Orientation

今回対応させたい向きにチェックを入れます。
スクリーンショット 2020-03-18 23.53.11.png

2. Info > Custom iOS Target Properties

+ボタンを押し、Supported interface orientationsの項目に今回対応させたい向きを入力します。
スクリーンショット 2020-03-18 23.53.24.png

3. 実機の向き固定の解除

実機の画面固定項目の解除も忘れずに
IMG_2022.jpg

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