私のメインのストーリーボードにはかなりシンプルな設定があります:
次のようにビューのアクティブビューの変更を処理するコードがあります。
import Foundation
import UIKit
class ViewController : UIViewController {
@IBOutlet weak var stackView: UIStackView!
@IBOutlet weak var segmentController: UISegmentedControl!
@IBAction func SegmentClicked(_ sender: AnyObject) {
updateView(segment: sender.titleForSegment(at: sender.selectedSegmentIndex)!)
}
override func viewDidLoad() {
updateView(segment: "First")
}
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}
}
}
ご覧のように、「First」というタブを選択すると、インデックス1のサブビューが表示され、2は非表示になります。それ以外を選択すると、インデックス2のサブビューが表示され、1は非表示になります。
これは、ビューをゆっくりと変更すると最初は機能するように見えますが、少し速くすると、数回クリックした後、インデックス1のビューが永続的に非表示のままになり、インデックス0のビューが画面全体に表示されます。問題を示すアニメーションとストーリーボードのスクリーンショットを下に配置しました。出力は、問題が発生した場合、最初のセグメントをクリックすると両方のビューが非表示のままであることを示しています。
なぜこれが起こっているのか誰かに教えてもらえますか?これはバグですか、それとも私はあるべきことをしていませんか?
よろしくお願いします!
更新:最初のセグメント> 2番目のセグメント> 3番目のセグメント> 2番目のセグメント>最初のセグメントの順に移動すると、問題を確実に再現できるようです。
結局、ここですべての提案を試しても、なぜこのように動作していたのかわからなかったため、Appleから連絡を受け、バグレポートの提出を依頼されました。ただし、最初に両方のビューを再表示することで回避策を見つけて、私の問題を解決しました:
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = false
if(segment == "First") {
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
}
}
}
バグは、スタックビューでのビューの非表示と表示が累積されることです。奇妙なAppleバグ。スタックビューでビューを2回非表示にした場合、元に戻すには2回表示する必要があります。3回表示した場合は、3回非表示にする必要があります。実際にはそれを隠します(開始時に隠されていたと仮定します)。
これはアニメーションの使用とは無関係です。
したがって、コードで次のようなことを行い、表示されている場合にのみビューを非表示にすると、この問題を回避できます。
if myView.isHidden == false {
myView.isHidden = true
}
Dave Battonによるいい答えに基づいて、UIView拡張機能を追加して、呼び出しサイトを少しすっきりとしたIMOにすることもできます。
extension UIView {
var isHiddenInStackView: Bool {
get {
return isHidden
}
set {
if isHidden != newValue {
isHidden = newValue
}
}
}
}
次に、stackView.subviews[someIndex].isHiddenInStackView = false
を呼び出すことができます。これは、ifステートメントの束ではなく、スタックビュー内で管理するビューが複数ある場合に役立ちます。
私が見ることができることに基づいて、この奇妙な動作はアニメーションの継続時間によって引き起こされます。ご覧のように、アニメーションが完了するまで1秒かかりますが、segmentControlの切り替えをそれより速く開始すると、それがこの動作の原因であると私は主張します。
あなたがすべきことは、メソッドが呼び出されたときにユーザーの対話性を非アクティブにし、アニメーションが完了したら再度有効にすることです。
次のようになります。
func updateView(segment: String) {
segmentControl.userInteractionEnabled = false
UIView.animateWithDuration(1.0, animations: {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}, completion: {(finished: Bool) in
segmentControl.userInteractionEnabled = true
}
}
これは高速な切り替え(欠点として見えるかもしれません)を防ぎますが、これを解決するために私が知っている唯一の他の方法は、アニメーションを完全に削除することです。
スタックビューとサブビュー、特にセグメント化されたコントロールの構成と自動レイアウト制約を確認します。
セグメント化されたコントロールはスタックビューの設定を複雑にするので、セグメント化されたコントロールをスタックビューから取り出し、メインビューに対して制約を設定します。
セグメント化されたコントロールがスタックビューの外にある場合、コードが正しく機能するようにスタックビューを設定するのは比較的簡単です。
スタックビューの制約をリセットして、セグメント化されたコントロールの下に配置し、スーパービューの残りの部分をカバーします。属性インスペクタで、配置を塗りつぶしに、分布を均等に塗りつぶし、コンテンツモードを塗りつぶしにスケールに設定します。
サブビューの制約を削除し、コンテンツモードを[塗りつぶしにスケール]に設定します。
コード内のArrangedSubviewのインデックスを調整すると、自動的に機能するはずです。