UIStackView的显示/隐藏动画

3
在 stack view 中,我有 UIPickerView,并希望在按钮点击时折叠和展开它。我想使用简单的动画,但不知道如何实现,我尝试了许多方法,但没有一种能达到正确的效果,我总是得到这个:iOS 10 animation 我想让选择器也折叠,但它没有。 它只是在动画之后消失了,看起来不好看。
我的代码如下,其中 self 是 UIStackView:
UIView.animate(withDuration: 0.3, animations: { [unowned self] in
        self.picker.isHidden = !open
        self.layoutIfNeeded()
    })

1
动态添加/删除UITableView中的行可能比UIStackView更好。 - Sweeper
我认为UIStackView是设计用来动态地在运行时更改内容的。 - eja08
不,那是 UITableView 的一个目的。UIStackView 用于在水平或垂直方向上轻松地以直线方式布置视图。 - Sweeper
3个回答

11

对于某些事情来说,Stack View 的自动显示/隐藏动画效果非常好。但对于像 Picker View 这样的东西来说,效果就不太好(正如你所看到的)。

一种方法是:

  • 将 Picker View 嵌入到一个普通视图中
  • 垂直居中约束它
  • 为包含视图添加默认高度(比 Picker View 稍微高一点)
  • 动画视图的高度约束

Picker View 不会自己“收缩”,所以你会看到 Picker View “消失”。如果你想要在动画过程中让它“收缩”,还需要对其进行变换动画。

以下是一个示例(我使用对比色使元素更易于查看,并且我减慢了动画持续时间以使其更加明显):

enter image description here

以下是示例代码:

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
            }
        }
    }

}

使用这个布局:

在此输入图片描述

以下是故事板的源代码(您可以快速尝试自己的代码):

<?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" width="375" height="667"/>
                        <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" width="359" height="338"/>
                                <subviews>
                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="clh-vv-1e4">
                                        <rect key="frame" x="0.0" y="0.0" width="359" height="50"/>
                                        <subviews>
                                            <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="VMQ-JX-yNt">
                                                <rect key="frame" x="8" y="8" width="343" height="34"/>
                                                <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" width="163.5" height="34"/>
                                                        <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" width="163.5" height="34"/>
                                                        <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" width="359" height="232"/>
                                        <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" width="343" height="216"/>
                                                <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" width="359" height="40"/>
                                        <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>

非常感谢您的回答!它给了我一种在互联网上找不到的见解! - eja08

0

您可以在动画代码中使用usingSpringWithDamping


0

Swift 5: 上滑动画

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

2- 将前导和尾随设置为安全区域。

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

4- 从第3行创建一个IBOutlet,类型为NSLayoutConstraint optional。

然后:

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 标准动画一样工作。

祝你好运。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接