UIStackView 显示/隐藏动画

Posted

技术标签:

【中文标题】UIStackView 显示/隐藏动画【英文标题】:UIStackView show/hide animation 【发布时间】:2019-09-19 09:11:45 【问题描述】:

在堆栈视图中,我有 UIPickerView,我想在点击按钮时折叠和展开它。我想使用一个简单的动画,但不知道如何实现它,我尝试了很多方法,但都没有导致正确的外观我总是得到这个

我想让选择器也崩溃,但它不会。它只是在动画后消失,看起来不太好。

我的代码中 self 是 UIStackView:

UIView.animate(withDuration: 0.3, animations:  [unowned self] in
        self.picker.isHidden = !open
        self.layoutIfNeeded()
    )

【问题讨论】:

UITableView 中动态添加/删除一行可能比UIStackView 更好。 我认为 UIStackView 旨在随时随地动态更改内容。 不,这是UITableView 的目的之一。 UIStackView 用于轻松水平或垂直布置视图。 【参考方案1】:

堆栈视图的自动显示/隐藏动画效果很好 --- 对于 一些 的东西。对于其他人来说,比如使用 Picker View,就没有那么多了(如你所见)。

一种方法是:

将选取器视图嵌入到常规视图中 限制它垂直居中 为包含视图添加默认高度(例如比选择器视图略高) 为视图的高度约束设置动画

不过,选择器视图不会自行“挤压”,因此您会得到一个“消失”的选择器视图。如果您希望它在动画时“挤压”,您还需要对其变换进行动画处理

这是一个示例(我使用对比色使元素更容易看到,并且我已经放慢了动画持续时间以使其更明显):

这里是示例代码:

class StackDemoViewController: UIViewController 

    @IBOutlet var pickerHolderView: UIView!
    @IBOutlet var pickerHolderHeightConstraint: NSLayoutConstraint!

    @IBOutlet var normalButton: UIButton!
    @IBOutlet var squeezeButton: UIButton!

    @IBOutlet var thePickerView: UIDatePicker!

    // this will be assigned in viewDidLoad
    var defaultPickerHolderViewHeight: CGFloat = 0.0

    // anim duration - change to something like 1.0 to see the effect in "slo-motion"
    let animDuration = 0.3

    override func viewDidLoad() 
        super.viewDidLoad()

        // get the original picker holder view height constant
        defaultPickerHolderViewHeight = pickerHolderHeightConstraint.constant
    

    @IBAction func normalAnim(_ sender: Any) 

        // local bool
        let bIsHidden = pickerHolderView.isHidden

        // if the picker holder view is currently hidden, show it
        if bIsHidden 
            pickerHolderView.isHidden = false
        

        // if picker holder height constant is > 0 (it's open / showing)
        //      set it to 0
        // else
        //      set it to defaultPickerHolderViewHeight
        self.pickerHolderHeightConstraint.constant = self.pickerHolderHeightConstraint.constant > 0 ? 0 : defaultPickerHolderViewHeight

        // animate the change
        UIView.animate(withDuration: animDuration, animations: 
            self.view.layoutIfNeeded()
        )  finished in
            // if the picker holder view was showing (NOT hidden)
            //  hide it
            if !bIsHidden 
                self.pickerHolderView.isHidden = true
                // disable squeeze button until view is showing again
                self.squeezeButton.isEnabled = false
             else 
                // re-enable squeeze button
                self.squeezeButton.isEnabled = true
            
        
    

    @IBAction func squeezeAnim(_ sender: Any) 

        // local bool
        let bIsHidden = pickerHolderView.isHidden

        var t = CGAffineTransform.identity

        // if the picker holder view is currently hidden, show it
        if bIsHidden 
            pickerHolderView.isHidden = false
         else 
            // we're going to hide it
            t = CGAffineTransform(scaleX: 1.0, y: 0.01)
        

        // if picker holder height constant is > 0 (it's open / showing)
        //      set it to 0
        // else
        //      set it to defaultPickerHolderViewHeight
        self.pickerHolderHeightConstraint.constant = self.pickerHolderHeightConstraint.constant > 0 ? 0 : defaultPickerHolderViewHeight

        // animate the change
        UIView.animate(withDuration: animDuration, animations: 
            self.thePickerView.transform = t
            self.view.layoutIfNeeded()
        )  finished in
            // if the picker holder view was showing (NOT hidden)
            //  hide it
            if !bIsHidden 
                self.pickerHolderView.isHidden = true
                // disable normal button until view is showing again
                self.normalButton.isEnabled = false
             else 
                // re-enable normal button
                self.normalButton.isEnabled = true
            
        
    


使用此布局:

而且,这里是 Storyboard 的来源(所以你可以自己快速尝试):

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="ios.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Zg0-f1-bBK">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Stack Demo View Controller-->
        <scene sceneID="Itw-fL-6gO">
            <objects>
                <viewController id="Zg0-f1-bBK" customClass="StackDemoViewController" customModule="TranslateTest" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="rze-A8-JnC">
                        <rect key="frame" x="0.0" y="0.0"  />
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="vDP-gh-oah">
                                <rect key="frame" x="8" y="120"  />
                                <subviews>
                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="clh-vv-1e4">
                                        <rect key="frame" x="0.0" y="0.0"  />
                                        <subviews>
                                            <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="VMQ-JX-yNt">
                                                <rect key="frame" x="8" y="8"  />
                                                <subviews>
                                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Zb9-rN-qPb">
                                                        <rect key="frame" x="0.0" y="0.0"  />
                                                        <color key="backgroundColor" red="0.99806135890000003" green="0.96808904409999996" blue="0.12760734560000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <state key="normal" title="Normal"/>
                                                        <connections>
                                                            <action selector="normalAnim:" destination="Zg0-f1-bBK" eventType="touchUpInside" id="zwU-Bs-ZlI"/>
                                                        </connections>
                                                    </button>
                                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v2b-2E-upp">
                                                        <rect key="frame" x="179.5" y="0.0"  />
                                                        <color key="backgroundColor" red="0.99806135890000003" green="0.96808904409999996" blue="0.12760734560000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <state key="normal" title="With Squeeze"/>
                                                        <connections>
                                                            <action selector="squeezeAnim:" destination="Zg0-f1-bBK" eventType="touchUpInside" id="ARc-fQ-XRE"/>
                                                        </connections>
                                                    </button>
                                                </subviews>
                                            </stackView>
                                        </subviews>
                                        <color key="backgroundColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <constraints>
                                            <constraint firstAttribute="trailing" secondItem="VMQ-JX-yNt" secondAttribute="trailing" constant="8" id="T0v-du-5Aj"/>
                                            <constraint firstItem="VMQ-JX-yNt" firstAttribute="top" secondItem="clh-vv-1e4" secondAttribute="top" constant="8" id="Y2j-KP-ylE"/>
                                            <constraint firstItem="VMQ-JX-yNt" firstAttribute="leading" secondItem="clh-vv-1e4" secondAttribute="leading" constant="8" id="mKK-5Q-IhS"/>
                                            <constraint firstAttribute="bottom" secondItem="VMQ-JX-yNt" secondAttribute="bottom" constant="8" id="uJf-Y8-Uun"/>
                                        </constraints>
                                    </view>
                                    <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6L1-Bv-SxB">
                                        <rect key="frame" x="0.0" y="58"  />
                                        <subviews>
                                            <datePicker contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" datePickerMode="dateAndTime" minuteInterval="1" translatesAutoresizingMaskIntoConstraints="NO" id="0A6-0Z-m7u">
                                                <rect key="frame" x="8" y="8"  />
                                                <color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                <date key="date" timeIntervalSinceReferenceDate="590598642.83352995">
                                                    <!--2019-09-19 15:10:42 +0000-->
                                                </date>
                                            </datePicker>
                                        </subviews>
                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                        <constraints>
                                            <constraint firstItem="0A6-0Z-m7u" firstAttribute="centerY" secondItem="6L1-Bv-SxB" secondAttribute="centerY" id="Eqi-Od-JBH"/>
                                            <constraint firstItem="0A6-0Z-m7u" firstAttribute="leading" secondItem="6L1-Bv-SxB" secondAttribute="leading" constant="8" id="IEp-7K-buG"/>
                                            <constraint firstAttribute="height" constant="232" id="e1y-wA-jqj"/>
                                            <constraint firstAttribute="trailing" secondItem="0A6-0Z-m7u" secondAttribute="trailing" constant="8" id="hLe-WM-Qnx"/>
                                        </constraints>
                                    </view>
                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Standard UILabel" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X5m-RD-zx4">
                                        <rect key="frame" x="0.0" y="298"  />
                                        <color key="backgroundColor" red="0.46202266219999999" green="0.83828371759999998" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <constraints>
                                            <constraint firstAttribute="height" constant="40" id="4c2-X0-9Kb"/>
                                        </constraints>
                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                        <nil key="textColor"/>
                                        <nil key="highlightedColor"/>
                                    </label>
                                </subviews>
                            </stackView>
                        </subviews>
                        <color key="backgroundColor" red="0.52747867609999999" green="1" blue="0.55622484120000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="k9S-Qf-yG1" firstAttribute="trailing" secondItem="vDP-gh-oah" secondAttribute="trailing" constant="8" id="5C9-Ef-syQ"/>
                            <constraint firstItem="vDP-gh-oah" firstAttribute="top" secondItem="k9S-Qf-yG1" secondAttribute="top" constant="100" id="cuG-HE-aDz"/>
                            <constraint firstItem="vDP-gh-oah" firstAttribute="leading" secondItem="rze-A8-JnC" secondAttribute="leading" constant="8" id="f5f-qW-BJ2"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="k9S-Qf-yG1"/>
                    </view>
                    <connections>
                        <outlet property="normalButton" destination="Zb9-rN-qPb" id="0sr-a2-wa9"/>
                        <outlet property="pickerHolderHeightConstraint" destination="e1y-wA-jqj" id="t7m-zQ-RwA"/>
                        <outlet property="pickerHolderView" destination="6L1-Bv-SxB" id="hkf-zy-GIS"/>
                        <outlet property="squeezeButton" destination="v2b-2E-upp" id="fFe-hm-qzd"/>
                        <outlet property="thePickerView" destination="0A6-0Z-m7u" id="ubt-fR-mx9"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="e1N-yd-USh" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="2244" y="126.38680659670166"/>
        </scene>
    </scenes>
</document>

【讨论】:

非常感谢您的回答!它给了我一个我在互联网上找不到的洞察力!【参考方案2】:

您可以将usingSpringWithDamping与动画代码一起使用

【讨论】:

【参考方案3】:

Swift 5:上滑动画

1- 将高度设置为 216(PickerView 标准)。

2- 设置引导到安全区域的尾随。

3- 将底部设置为“SuperViewBottom”为 -216

4- 将第 3 行的 IBOutlet 设为可选的 NSLayoutConstraint。

然后:

import UIKit

class ViewController: UIViewController 


@IBOutlet weak var bottom: NSLayoutConstraint!


 override func viewDidLoad() 

 super.viewDidLoad()

 bottom.constant = -216




-(IBAction)button:(id)sender

   UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations:
        
            self.bottom.constant = 0
        self.view.layoutIfNeeded()
        )  (AnimationComplete ) in 


 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
       

        UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations:
        
            self.bottom.constant = -216
        self.view.layoutIfNeeded()
        )  (AnimationComplete ) in 
   
    




它应该像 Apple 标准动画一样工作。

祝你好运?

【讨论】:

以上是关于UIStackView 显示/隐藏动画的主要内容,如果未能解决你的问题,请参考以下文章

UIStackView,通过调整动画大小隐藏子视图

UIStackView 隐藏视图动画

UIStackView中UILabels上的动画隐藏属性导致不同的动画

UIStackView 动画问题

取消隐藏 UIStackView 元素时的动画方向

UIStackView 子视图仅在 isHidden 设置为 false 时才动画