31648


プログラミング初心者です。Tab Bar Controllerについて質問があります。

私が使っているのはXcode Version 8.1、Swiftです。

カウントダウンタイマーとTo Do Listを別々のプロジェクトで作り終わり、その二つのアプリを一つのアプリにまとめたいと思っています。  
新しいプロジェクトを作り、Tab Bar Controllerを使いながら二つのアプリをもう一度作り直していったのですが、シミュレーターを起動させると

> Thread 1: signal SIGABRT

という赤いエラーがAppDelegateの

[code]

    class AppDelegate: UIResponder, UIApplicationDelegate {
    
[/code]

に発生し、シミュレーターが止まります。  
エラーの説明のようなものには

[code]

    > 2017-01-06 19:55:12.188 PP Final Product[60637:3223775] Unknown class PomodoroViewController in Interface Builder file.
    2017-01-06 19:55:12.571 PP Final Product[60637:3223775] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIViewController 0x7fddcff03760> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key lbTimer.'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x000000010242734b __exceptionPreprocess + 171
        1   libobjc.A.dylib                     0x0000000101e8821e objc_exception_throw + 48
        2   CoreFoundation                      0x0000000102427299 -[NSException raise] + 9
        3   Foundation                          0x000000010199826f -[NSObject(NSKeyValueCoding) setValue:forKey:] + 291
        4   UIKit                               0x00000001029e44ef -[UIViewController setValue:forKey:] + 88
        5   UIKit                               0x0000000102c5879e -[UIRuntimeOutletConnection connect] + 109
        6   CoreFoundation                      0x00000001023cc590 -[NSArray makeObjectsPerformSelector:] + 256
        7   UIKit                               0x0000000102c57122 -[UINib instantiateWithOwner:options:] + 1867
        8   UIKit                               0x00000001029eac21 -[UIViewController _loadViewFromNibNamed:bundle:] + 386
        9   UIKit                               0x00000001029eb543 -[UIViewController loadView] + 177
        10  UIKit                               0x00000001029eb878 -[UIViewController loadViewIfRequired] + 201
        11  UIKit                               0x00000001029ec0cc -[UIViewController view] + 27
        12  UIKit                               0x0000000102a4a2df -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 483
        13  UIKit                               0x0000000102a49721 -[UITabBarController transitionFromViewController:toViewController:] + 59
        14  UIKit                               0x0000000102a455e2 -[UITabBarController _setSelectedViewController:] + 365
        15  UIKit                               0x0000000102a45464 -[UITabBarController setSelectedViewController:] + 234
        16  UIKit                               0x00000001029036e6 +[UIView(Animation) performWithoutAnimation:] + 90
        17  UIKit                               0x0000000102a3fa00 -[UITabBarController _selectDefaultViewControllerIfNecessaryWithAppearanceTransitions:] + 354
        18  UIKit                               0x0000000102a40b7a -[UITabBarController viewWillAppear:] + 206
        19  UIKit                               0x00000001029f1a0f -[UIViewController _setViewAppearState:isAnimating:] + 692
        20  UIKit                               0x00000001029f211f -[UIViewController __viewWillAppear:] + 147
        21  UIKit                               0x00000001029f3913 -[UIViewController viewWillMoveToWindow:] + 507
        22  UIKit                               0x00000001028fa151 -[UIView(Hierarchy) _willMoveToWindow:withAncestorView:] + 621
        23  UIKit                               0x000000010290acf0 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 451
        24  UIKit                               0x00000001028f97a1 -[UIView(Hierarchy) addSubview:] + 838
        25  UIKit                               0x00000001028b5f5b -[UIWindow addRootViewControllerViewIfPossible] + 849
        26  UIKit                               0x00000001028b63a2 -[UIWindow _setHidden:forced:] + 293
        27  UIKit                               0x00000001028c9cb5 -[UIWindow makeKeyAndVisible] + 42
        28  UIKit                               0x0000000102842c89 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
        29  UIKit                               0x0000000102848de9 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
        30  UIKit                               0x0000000102845f69 -[UIApplication workspaceDidEndTransaction:] + 188
        31  FrontBoardServices                  0x0000000106238723 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
        32  FrontBoardServices                  0x000000010623859c -[FBSSerialQueue _performNext] + 189
        33  FrontBoardServices                  0x0000000106238925 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
        34  CoreFoundation                      0x00000001023cc311 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
        35  CoreFoundation                      0x00000001023b159c __CFRunLoopDoSources0 + 556
        36  CoreFoundation                      0x00000001023b0a86 __CFRunLoopRun + 918
        37  CoreFoundation                      0x00000001023b0494 CFRunLoopRunSpecific + 420
        38  UIKit                               0x00000001028447e6 -[UIApplication _run] + 434
        39  UIKit                               0x000000010284a964 UIApplicationMain + 159
        40  PP Final Product                    0x000000010189782f main + 111
        41  libdyld.dylib                       0x0000000105aa168d start + 1
        42  ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb) 
    
[/code]

と書かれているのですが、いまいち理解できません。同じような質問をされている方がたくさんいたので、その解決方法（connectionを確認する、再起動する、など）をすべて試してみたのですが、直りませんでした。

問題はなんなのでしょうか？  
また、Tab Bar Controllerの使い方や、複数のアプリを一つにまとめる方法は合っていますでしょうか？

AppDelegate.swift

[code]

    import UIKit
    
    @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
    
        internal var window: UIWindow?
    
        private var myTabBarController: UITabBarController!
    
        private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?)
    -> Bool {
    
            window = UIWindow(frame: UIScreen.main.bounds)
    
            let myFirstTab: UIViewController = PomodoroViewController()
            let mySecondTab: UIViewController = ToDoListViewController()
    
            let myTabs = NSArray(objects: myFirstTab, mySecondTab)
    
            myTabBarController = UITabBarController()
    
            myTabBarController?.setViewControllers(myTabs as? [UIViewController], animated: false)
    
            self.window!.rootViewController = myTabBarController
    
            self.window!.makeKeyAndVisible()
    
            return true
        } }
    
[/code]

PomodoroViewController.swift

[code]

    import UIKit
    
    class PomodoroViewController: UIViewController {
    
        init() {
            super.init(nibName: nil, bundle: nil)
    
            self.tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.featured, tag: 1)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        required override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: Bundle!) {
            super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        }
    
        @IBOutlet weak var lbTimer: UILabel!
    
        let pomodoroTime: TimeInterval = 60 * 25
    
        let formatter = DateFormatter()
    
        var theTime: TimeInterval = 0.0
    
        override func viewDidLoad(){
            super.viewDidLoad()
    
            formatter.dateFormat = "mm:ss"
    
            let startTime = Date(timeIntervalSinceReferenceDate: pomodoroTime)
            lbTimer.text = formatter.string(from: startTime)
        }
    
        override func didReceiveMemoryWarning(){
            super.didReceiveMemoryWarning()
        }
    
        @IBAction func countDown(_ sender: UIButton) {
    
            theTime = pomodoroTime
            Timer.scheduledTimer(timeInterval: 1.0,
                                 target: self,
                                 selector: #selector(PomodoroViewController.tickTimer(timer:)),
                                 userInfo: nil,
                                 repeats: true)
        }
    
        func tickTimer(timer: Timer){
    
            theTime -= 1.0
            let newTime = Date(timeIntervalSinceReferenceDate: theTime)
            lbTimer.text = formatter.string(from: newTime)
    
            if theTime < 0.1 {
                timer.invalidate()
            }
        }
         }
    
[/code]

ToDoListViewController.swift

[code]

    import UIKit
    
    class ToDoListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddToDoItemControllerDelegate {
    
        init() {
            super.init(nibName: nil, bundle: nil)
    
            self.tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.featured, tag: 2)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        required override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: Bundle!) {
            super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        }
    
        @IBOutlet weak var tableView: UITableView!
    
        var toDoList: NSMutableArray = ["Go get groceries", "Walk the dog", "Watch a movie", "Do your homework"]
    
        var completedToDoList: [Int:String] = [:]
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableView.delegate = self
            tableView.dataSource = self
        }
    
        override func viewWillAppear(_ animated: Bool) {
            resetAccessoryType()
            tableView.reloadData()
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return toDoList.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoCell", for: indexPath)
            cell.textLabel?.text = toDoList[indexPath.row] as? String
    
            return cell
        }
    
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        func resetAccessoryType() {
            for row in 0..<toDoList.count {
                if let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) {
                    cell.accessoryType = .none
                }
            }
        }
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            tableView.deselectRow(at: indexPath, animated: true)
    
            if let cell = tableView.cellForRow(at: indexPath) {
                if cell.accessoryType == .none {
                    cell.accessoryType = .checkmark
                    completedToDoList[completedToDoList.count] = toDoList[indexPath.row] as? String
                    toDoList.removeObject(at: indexPath.row)
                } else {
                    cell.accessoryType = .none
                }
            }
        }
    
        func addToDoItemToList(_ text:String) {
            toDoList[toDoList.count] = text
    
            tableView.reloadData()
        }
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if(segue.identifier == "AddToDoItemSegue") {
                let navigationController = segue.destination as! UINavigationController
                let addToDoItemViewController = navigationController.topViewController as! AddToDoItemController
    
                addToDoItemViewController.delegate = self
            } else if(segue.identifier == "CompletedToDoItemsSegue") {
                let completedToDoItemsController = segue.destination as! CompletedToDoItemsController
                completedToDoItemsController.completedToDoList = completedToDoList
            }
        }
         }
    
[/code]


CC BY-SA 3.0
2017-01-05T19:59:55.243
31645
2017-01-06T22:56:07.160
2017-01-06T18:58:34.767
20099
20099
question
swift ios xcode
Tab Bar Controller - エラー Thread 1: signal SIGABRT


AppDelegate.swiftに記述しているコードは、Storyboardなしでプロジェクトを構成するときのもので、Storyboardを使ったプロジェクトでは記述しません。プロジェクト作成時に戻しましょう。  
アプリケーション起動時、Storyboardはどうやってロードされるのか説明します。プロジェクト設定のGeneralを開くと、次の箇所があります。  
[![画像の説明をここに入力](https://i.stack.imgur.com/VlHLu.png)](https://i.stack.imgur.com/VlHLu.png)  
このMain
Interfaceに入力した名称のStoryboardが、起動時ロードされます。デフォルトでは、Main.storyboardがロードされます。余談ですが、Storyboardなしのプロジェクトにするときは、ここを空にします。

* * *

次に、Storyboardに`UITabBarController`を導入する方法を説明します。手っ取り早いのは、プロジェクト作成時に、Tabbed
Applicationのテンプレートから作成することです。

[![画像の説明をここに入力](https://i.stack.imgur.com/NFr9Y.png)](https://i.stack.imgur.com/NFr9Y.png)

そうでない場合は、Object LibraryからTab Bar Controllerをドラッグ＆ドロップします。

[![画像の説明をここに入力](https://i.stack.imgur.com/T2crB.png)](https://i.stack.imgur.com/T2crB.png)

Storyboardの編集では、Storyboardで最初にロードされるView
Controllerが何かを、まず意識することです。最初にロードするView Controllerを、Storyboardでは、 **Initial
View Controller** と呼びます。

[![画像の説明をここに入力](https://i.stack.imgur.com/SxZQO.png)](https://i.stack.imgur.com/SxZQO.png)

View Controllerの左側に、矢印がついて、それがInitial View Controllerであることを示しています。View
ControllerのAttributes Inspectorの「Is Initial View
Controller」にチェックをつけることで、Initial View
Controllerを変更することができます。現時点で、`UITabBarController`がInitial View
Controllerになっていなければ、変更してください。

* * *

`UITabBarController`のタブに、View Controllerを追加するには、右ボタン＋ドラッグ（Controlキー＋ドラッグ）で、Tab
Bar ControllerとView Controllerを結びます。ダイアログで、Relationship Segue、view
controllersを選びます。

[![画像の説明をここに入力](https://i.stack.imgur.com/wb3Dx.png)](https://i.stack.imgur.com/wb3Dx.png)

ここで注意してもらいたいことは、ToDoListのように、`UINavigationController`によって画面遷移する一連のView
Controllerは、Tab Bar Controllerと、Navigation Controllerをつなぐという点です。

[![画像の説明をここに入力](https://i.stack.imgur.com/DZev0.png)](https://i.stack.imgur.com/DZev0.png)

そうしないと、画面遷移が機能しなくなります。

* * *

_この回答は、質問者さんのレスポンスによって、内容を更新していく予定にしています。_


CC BY-SA 3.0
2017-01-06T00:09:02.340
31648
2017-01-06T22:56:07.160
2017-01-06T22:56:07.160
18540
18540
31645
answer


重要な1行が追加されたので、原因はほぼ特定できたという感じですが、またstoryboardの設定の問題ですね。

>
[code]

>     2017-01-06 19:55:12.188 PP Final Product[60637:3223775] Unknown class
> PomodoroViewController in Interface Builder file.
>  
[/code]

このエラーが発生するのは、storyboardに設定してある`PomodoroViewController`と言うクラス名が、アプリの実行時にはシステム側からは見えない(よってUnknown)状態になっていることを表しています。

この状態が発生すると、iOSはとりあえずプレーンな`UIViewController`をインスタンス化するため`lbTimer`と言う`@IBOutlet`をつなぎに行った時に、そんなものは`UIViewController`にはないので、
_this class is not key value coding-compliant for the key lbTimer._
と言う次の行のエラー(例外)につながるわけです。

一度`@IBOutlet`の接続に成功した状態ならそんな不整合は発生しては困るのですが、残念ながら現在のXcodeでは、storyboardとソースコードの編集を交互に行っていたりすると、ちょくちょく発生します。とりあえず再度storyboardの設定を確認してみてください。

`PomodoroViewController`を設定してあるはずのViewControllerをstoryboardエディター内で選択し、Inspector部分で以下の内容を確認してください。

[![CustomClass設定](https://i.stack.imgur.com/fnDPy.jpg)](https://i.stack.imgur.com/fnDPy.jpg)

上段の **Class** はもちろん`PomodoroViewController`になっていないといけないのですが、下段の **Module**
も上の図のように`Current -
{あなたのプロジェクト名}`になっていないといけません。Storyboardで不整合が発生していると、見かけ上は正しく設定されているように見えても内部的におかしいと言うこともあるので、一旦設定を削除してから設定し直したほうがいいかもしれません。

* * *

ちなみにAppDelegate.swift内のこのコードはiOS側から呼ばれることはありません。

[code]

    private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?)　-> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
    
        let myFirstTab: UIViewController = PomodoroViewController()
        let mySecondTab: UIViewController = ToDoListViewController()
    
        let myTabs = NSArray(objects: myFirstTab, mySecondTab)
    
        myTabBarController = UITabBarController()
    
        myTabBarController?.setViewControllers(myTabs as? [UIViewController], animated: false)
    
        self.window!.rootViewController = myTabBarController
    
        self.window!.makeKeyAndVisible()
    
        return true
    }
    
[/code]

`private`を付けているので、このメソッドの存在はiOS側には見えなくなっています。なんの働きもしていませんから、まるごと削除してしまうといいでしょう。将来アプリを拡張する場合に困ることもあるので、代わりに新規プロジェクトを作成した時にできるこのメソッドを入れておいてもいいかもしれません。

[code]

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
    
[/code]

(メソッドヘッダーで`_`の有無や`launchOptions`のデータ型が異なっているのに注意してください。後者がSwift
3での正しい`application(_:didFinishLaunchingWithOptions:)`メソッドの書き方になります。)

* * *

他にもまだ何かあるかもしれませんが、とりあえず上記のstoryboardの設定確認をしてみてください。


CC BY-SA 3.0
2017-01-06T22:03:36.903
31681
2017-01-06T22:03:36.903
13972
31645
answer