質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.62%

UIScrollViewの中のコンテンツ初期表示位置がずれてしまう

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,506

u-u

score 5

前提・実現したいこと

Swift初心者です。
iOSでカード表示アプリを作成しています。
1つの画面に2つのScrollViewを配置し、AutoLayoutの設定をしています。
起動してすぐに、ここに表示しているような状態になるようにしたいです。

イメージ説明

発生している問題・エラーメッセージ

起動時にScrollViewの中のコンテンツの表示位置がずれてしまいます。
デバイス回転時も同様です。
しかしModal画面を開き(タップした時に現れるツールバーを開く)、それを閉じると、正常の位置に表示されます。

異常画面

該当のソースコード

コードかAutoLayoutの設定かどちらが悪いのかわかっていないので、
プロジェクトを以下に挙げています。
GitHubのプロジェクト

一応ScrollView関連の設定のコードを抜粋します。
settingScrollViewとgenerateViewメソッドで実装しており、
viewWillAppearの中で呼んでいます。

//ScrollViewの設定
    func settingScrollView () {

        print("Setting View Size")

        //Page幅の設定
        //pageWidth = self.view.frame.size.width
        pageWidth = upperCardView.frame.size.width + CGFloat(pageMargin)

        //pageHeight
        pageHeight = upperCardView.frame.size.height + CGFloat(pageMargin)

        //ScrollViewのサイズ設定

        if isPortrait {
            //Portraitの時
            upperScrollView.bounds = CGRectMake(0, 0, pageWidth, upperScrollView.frame.height)
            lowerScrollView.bounds = CGRectMake(0, 0, pageWidth, lowerScrollView.frame.height)
//            upperScrollView.frame.size.width = pageWidth
//            lowerScrollView.frame.size.width = pageWidth
            upperScrollView.frame = upperScrollView.bounds

            //Bounceの設定
            upperScrollView.alwaysBounceVertical = false
            upperScrollView.alwaysBounceHorizontal = true
            lowerScrollView.alwaysBounceVertical = false
            lowerScrollView.alwaysBounceHorizontal = true

        } else {
            //Landscapeの時
            upperScrollView.bounds = CGRectMake(0, 0, upperScrollView.frame.width, pageHeight)
            lowerScrollView.bounds = CGRectMake(0, 0, lowerScrollView.frame.width, pageHeight)
//            upperScrollView.frame.size.height = pageHeight
//            lowerScrollView.frame.size.height = pageHeight
            upperScrollView.frame = upperScrollView.bounds

            //Bounceの設定
            upperScrollView.alwaysBounceVertical = true
            upperScrollView.alwaysBounceHorizontal = false
            lowerScrollView.alwaysBounceVertical = true
            lowerScrollView.alwaysBounceHorizontal = false

        }

        //はみ出したカードも表示
        upperScrollView.clipsToBounds = false
        lowerScrollView.clipsToBounds = false

    }

    //ScrollViewの中のPage生成
    func generateView () {


        //SubViewを削除
        removeAllSubviews(upperScrollView, jogaiSubView: upperCardView)
        removeAllSubviews(lowerScrollView, jogaiSubView: lowerCardView)


        //コンテンツ量に合わせてScrollViewのサイズを確保
        if isPortrait {
            upperScrollView.contentSize = CGSizeMake(pageWidth * CGFloat(upperCardString.count), upperScrollView.frame.height)
            lowerScrollView.contentSize = CGSizeMake(pageWidth * CGFloat(lowerCardString.count), lowerScrollView.frame.height)
        } else {
            upperScrollView.contentSize = CGSizeMake(upperScrollView.frame.width, pageHeight * CGFloat(upperCardString.count) )
            lowerScrollView.contentSize = CGSizeMake(lowerScrollView.frame.width, pageHeight * CGFloat(lowerCardString.count))
        }


        //viewの生成(Upper)
        print("generate view")

        for i in 0 ..< upperCardString.count {

            //CardViewを複製
            let genCardView = duplicateCardView(upperCardView, index: i)
            upperScrollView.addSubview(genCardView)

            //ラベルの裏ビューを複製
            let genLabelBG = duplicateBGView(upperCVlabelBG)
            genCardView.addSubview(genLabelBG)

            //ラベルView1を設定
            let genLabel1 = duplicateLabel(upperCVlabel1)
            genCardView.addSubview(genLabel1)
            //ラベルView2を設定
            let genLabel2 = duplicateLabel(upperCVlabel2)
            genCardView.addSubview(genLabel2)

            //テキストViewを設定
            let cardText = duplicateTextView(upperCVtext)
            genCardView.addSubview(cardText)

            //ラベルとテキストの中身を設定
            genLabel1.text = upperCardString[i][0]
            genLabel2.text = upperCardString[i][1]
            cardText.text = upperCardString[i][2]
        }


        //viewの生成(lower)
        for i in 0 ..< lowerCardString.count {

            //CardViewを複製
            let genCardView = duplicateCardView(lowerCardView, index: i)
            lowerScrollView.addSubview(genCardView)

            //ラベルの裏ビューを複製
            let genLabelBG = duplicateBGView(lowerCVlabelBG)
            genCardView.addSubview(genLabelBG)

            //ラベルView1を設定
            let genLabel1 = duplicateLabel(lowerCVlabel1)
            genCardView.addSubview(genLabel1)
            //ラベルView2を設定
            let genLabel2 = duplicateLabel(lowerCVlabel2)
            genCardView.addSubview(genLabel2)

            //テキストViewを設定
            let cardText = duplicateTextView(lowerCVtext)
            genCardView.addSubview(cardText)

            //ラベルとテキストの中身を設定
            genLabel1.text = lowerCardString[i][0]
            genLabel2.text = lowerCardString[i][1]
            cardText.text = lowerCardString[i][2]

        }

        //最初の表示位置の初期化
        print("Position Reset")
        upperScrollView.contentOffset = CGPointMake(0, 0);
        lowerScrollView.contentOffset = CGPointMake(0, 0);

    }

試したこと

上記のコードを、viewDidAppearの中で呼んでみたり、
UIScrollView.contentOffset = 0
をいろいろな所に入れてみたりしています。
また、AutoLayout使用以前は正しく表示されていました。

補足情報(言語/FW/ツール等のバージョンなど)

より詳細な情報
XCode7.3.1, Swift, iPhone6で検証しています

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • fuzzball

    2016/05/18 01:03

    回答に書くほどのことではないのでここに書きます。質問後すぐにプロジェクトを見ましたが、制約でエラーが出てますよね?その後、修正されていないのでしたら、まずはエラーを解消してみてはどうでしょうか。

    キャンセル

  • u-u

    2016/05/18 02:14

    ご指摘ありがとうございます。制約というのはAutoLayoutのConstraintsのことでしょうか。
    Missing...は解消できたのですが、Scrollable Content Size Ambiguityのエラーがわからず、しかしScrollView自体の位置は合ってそうなので後回し、というか放置していました。ここが真因なのであれば、質問の表題を変えたほうがよさそうですね・・・。
    ここはもしかしてCodeの中で記述しているcontentSizeが効くのではないかと思って、呼ぶタイミングをいろいろ変えてみたりというのはやってみました。StoryBoard上の設定の方に問題があるのでしょうか?

    キャンセル

回答 1

checkベストアンサー

0

起動直後にレイアウトがおかしいのは、Autolayoutによるレイアウトが完了する前に座標などを参照しているせいだと思いますので、例えば、settingScrollView()の呼び出し前にレイアウトを更新すると正しい(かも知れない)レイアウトになります。

:
print("[before]", upperCardView.frame)
self.view.layoutIfNeeded() //レイアウト更新
print("[after]", upperCardView.frame)

//Pageの設定
settingScrollView()
    :

レイアウト更新の前後でupperCardView.frameが変化しているのが分かると思います。


あと、viewWillLayoutSubviewsとviewDidLayoutSubviewsが呼び出されるタイミングを理解/把握しておきましょう。

override func viewWillLayoutSubviews() {
    print("viewWillLayoutSubviews")
    super.viewWillLayoutSubviews()
}

override func viewDidLayoutSubviews() {
    print("viewDidLayoutSubviews")
    super.viewDidLayoutSubviews()
}

layoutIfNeeded()でこれらの関数が呼び出されていることが分かります。


もう一つ、ついでに書いておきますが、viewWillAppearやviewDidAppearでは、スーパークラスの呼び出しを行って下さい。

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
        :
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
        :
}

最初に書いた修正方法は付け焼刃的な対応ですので、もう少しレイアウトについての理解を深めた後で、より良い修正を行うことをお勧めします。
また、Storyboard上での制約(Constraint)のエラーも取り除いた方が良いと思います。Has ambiguous scrollable content xxxはググれば解決方法は見つかるでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/19 00:26

    ご丁寧な回答ありがとうございます。
    self.view.layoutIfNeeded()
    の前後でupperCardView.frameが変化していること、確認できました。
    また、起動時のレイアウトについてはViewDidAppearのところでレイアウト更新&settingScrollViewを呼び出すことで正しい初期位置になることを確認できました。
    ただ、ご指摘の通り付け焼き刃の対応になっているようで、回転すると乱れたりします。

    今回教えていただいたことを手掛かりに、他の不具合への対処、より良い修正を検討します。
    Has ambiguous scrollable content・・についても早速それらしきものが幾つか見つかりました。なぜこれを自分で検索かけなかったのか、我ながら不思議ですが・・。

    考えられるところを適当にいじっていてわけがわからなくなっていたのですが、道筋が見えて大変助かりました。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる