Обработка представления, содержащего подпредставление, которое может свернуться и развернуться - PullRequest
0 голосов
/ 12 декабря 2018

В моем контроллере представления у меня есть представление, предназначенное для размещения подпредставления.Давайте назовем это ViewA.Когда контроллер загружается, представление, загруженное из пера, устанавливается как подпредставление внутри ViewA.В зависимости от того, что находится в подпредставлении, его высота может быть разных размеров.

Поэтому я создал делегата, который будет оповещать об изменении высоты подпредставления, уведомляя его родителя об обновлении его собственной высоты:

UIViewController

class MyViewController: UIViewController, MyViewDelegate {

    @IBOutlet weak var myView: UIView!
    @IBOutlet weak var myViewHeightConstraint: NSLayoutConstraint!

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

        let mySubView: MySubView = UINib(nibName: "MySubView", bundle: nil).instantiate(withOwner: MySubView(), options: nil)[0] as! MySubView
        mySubview.translatesAutoresizingMaskIntoConstraints = false
        mySubView.delegate = self
        myView.addSubView(mySubView)

        let leadingConstraint = NSLayoutConstraint(item: mySubView, attribute: .leading, relatedBy: .equal, toItem: myView, attribute: .leading, multiplier: 1, constant: 0)
        let trailingConstraint = NSLayoutConstraint(item: mySubView, attribute: .trailing, relatedBy: .equal, toItem: myView, attribute: .trailing, multiplier: 1, constant: 0)
        let topConstraint = NSLayoutConstraint(item: mySubView, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1, constant: 0)
        let bottomConstraint = NSLayoutConstraint(item: mySubView, attribute: .bottom, relatedBy: .equal, toItem: myView, attribute: .bottom, multiplier: 1, constant: 0)
        NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])

    }

    override func viewDidLayoutSubviews() {
        addShadowToView()
    }

    func addShadowToView() {
        myView.layer.masksToBounds = false
        myView.layer.shadowColor = UIColor.black.cgColor
        myView.layer.shadowOpacity = 0.25
        myView.layer.shadowOffset = CGSize(width: 0, height: 0)
        myView.layer.shadowRadius = 5.0
        myView.layer.shadowPath = UIBezierPath(rect. myView.bounds).cgPath
    }


    MySubViewDelegate(_ mySubView: MySubView, didUpdateHeightTo height: CGFloat) {
        myViewHeightConstraint.constant = height
        myView.updateConstraints()
        addShadowToView()
    } 
}

MySubView

class MySubView: UIView {

    var delegate: MySubViewDelegate?

    @IBOutlet weak var aView: UIView!
    @IBOutlet weak var aViewHeghtConstraint: NSLayoutConstraint!\

    var isViewCollapsed = false

    @IBAction func toggleView() {

        aViewHeightConstraint.contant = isViewCollapsed ? 100 : 0
        isViewCollapsed = !isViewCollapsed

        updateConstraints()

        delegate.MSView(self, didUpdateHeightTo height: self.frame.height)
    }
}

protocol MySubViewDelegate {
    func MSView(_ MySubView: MySubView, didUpdateHeightTo height: CGFloat)
} 

Есть ли лучший способ размещения подпредставления, которое расширяется и сворачивается в родительское представление, которое сможет обновить свой собственный фрейм для размещения его дочернего элемента?изменения?

Ответы [ 2 ]

0 голосов
/ 12 декабря 2018

Вы должны вызвать layoutIfNeeded после обновления константы ограничения для принудительного обновления макета перед получением новой высоты:

print("Before:", self.frame.height)
heightConstraint.constant += 10
layoutIfNeeded()
print("After:", self.frame.height)

Результат:

Before: 132.0
After: 142.0
Before: 142.0
After: 152.0
Before: 152.0
After: 162.0
0 голосов
/ 12 декабря 2018

После комментариев и размещенного кода ...

Похоже, вы делаете много вещей, которые вам не нужны.

При надлежащих ограничениях вам потребуется гораздо меньше кода, и вам вовсе не нужен ваш протокол / делегат.

Сначала - и это только подсказка - вы можете определить ограничения в кодев гораздо более простой и удобочитаемой форме:

    myView.addSubview(mySubView)

    NSLayoutConstraint.activate([
        mySubView.topAnchor.constraint(equalTo: myView.topAnchor, constant: 0.0),
        mySubView.bottomAnchor.constraint(equalTo: myView.bottomAnchor, constant: 0.0),
        mySubView.leadingAnchor.constraint(equalTo: myView.leadingAnchor, constant: 0.0),
        mySubView.trailingAnchor.constraint(equalTo: myView.trailingAnchor, constant: 0.0),
        ])

Второй - также совет - если вы создаете подкласс "теневого" представления, он может самостоятельно обрабатывать обновление вашей тени:

class MyShadowedView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    func commonInit() -> Void {
        // non-changing properties - set on init
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.25
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowRadius = 5.0
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        // update the shadowPath
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
    }

}

Когда вы добавляете myView к вашей раскадровке как UIView, просто присвойте его классу MyShadowedView, и вам не нужно делать какие-либо вызовы, чтобы добавить тень - он сделает это сам.

В-третьих - на основании размещенного вами кода, похоже, что у вашего myView есть ограничение по высоте, и вы ограничиваете mySubView его верхом и низом.Это означает, что mySubView будет высотой myView, и это никогда не изменится.Ваша функция «делегата» пытается изменить ее, но она всегда передает свою ограниченную высоту.

Итак ...

В вашем ViewController вы хотите добавить UIView, присвойте его классу MyShadowedView, присвойте ему ограничения Top, Leading и Trailing, как обычно (или Top, Width и CenterX, если это то, что вам нужно).

Для его высоты укажитеограничение высоты того, с чего следует начинать, , но делает это ограничение заполнителем, который будет удален во время выполнения.Это позволяет вам видеть его во время проектирования (и избегать жалоб IB на отсутствующие ограничения), но во время выполнения добавляемое подпредставление будет контролировать его высоту:

enter image description here

Ваш xib будет выглядеть следующим образом (ну, упрощенно - я уверен, что в нем больше элементов):

enter image description here

Примечание: предоставлениеНижнее ограничение aView с приоритетом 999 также помогает избежать предупреждений об ограничениях IB.

Когда вы нажимаете кнопку, ваш код переключает постоянную ограничения высоты aView между 100 и * 1056.*.Это расширит / свернет высоту его суперпредставления, которое будет контролировать высоту, если его суперпредставление (myView в контроллере представления).

Ваш полный код в конечном итоге будет:

//
//  TannerViewController.swift
//
//  Created by Don Mag on 12/12/18.
//

import UIKit

class MySubView: UIView {

    @IBOutlet weak var aView: UIView!
    @IBOutlet weak var aViewHeightConstraint: NSLayoutConstraint!

    var isViewCollapsed = false

    @IBAction func toggleView(_ sender: Any) {
        aViewHeightConstraint.constant = isViewCollapsed ? 100 : 0
        isViewCollapsed = !isViewCollapsed
    }

}

class TannerViewController: UIViewController {

    @IBOutlet weak var myView: MyShadowedView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let mySubView: MySubView = UINib(nibName: "MySubView", bundle: nil).instantiate(withOwner: MySubView(), options: nil)[0] as! MySubView
        mySubView.translatesAutoresizingMaskIntoConstraints = false
        myView.addSubview(mySubView)

        NSLayoutConstraint.activate([
            mySubView.topAnchor.constraint(equalTo: myView.topAnchor, constant: 0.0),
            mySubView.bottomAnchor.constraint(equalTo: myView.bottomAnchor, constant: 0.0),
            mySubView.leadingAnchor.constraint(equalTo: myView.leadingAnchor, constant: 0.0),
            mySubView.trailingAnchor.constraint(equalTo: myView.trailingAnchor, constant: 0.0),
            ])

    }

}

class MyShadowedView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    func commonInit() -> Void {
        // non-changing properties - set on init
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.25
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowRadius = 5.0
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        // update the shadowPath
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
    }

}

В результате:

enter image description here

enter image description here


Чтобы легко проверить этоВот источник раскадровки:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="jPc-3G-hfP">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Tanner View Controller-->
        <scene sceneID="f8L-af-3cE">
            <objects>
                <viewController id="jPc-3G-hfP" customClass="TannerViewController" customModule="SW4Temp" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="0I2-oK-Mx2">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uKj-M5-owl" customClass="MyShadowedView" customModule="SW4Temp" customModuleProvider="target">
                                <rect key="frame" x="40" y="120" width="295" height="100"/>
                                <color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="100" placeholder="YES" id="qvT-aM-Weq" userLabel="Placeholder Height = 100"/>
                                </constraints>
                            </view>
                        </subviews>
                        <color key="backgroundColor" red="0.99953407049999998" green="0.98835557699999999" blue="0.47265523669999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="uKj-M5-owl" firstAttribute="top" secondItem="twx-NV-wpY" secondAttribute="top" constant="100" id="141-8C-ZNl"/>
                            <constraint firstItem="twx-NV-wpY" firstAttribute="trailing" secondItem="uKj-M5-owl" secondAttribute="trailing" constant="40" id="5Zs-Or-GhR"/>
                            <constraint firstItem="uKj-M5-owl" firstAttribute="leading" secondItem="twx-NV-wpY" secondAttribute="leading" constant="40" id="R95-i1-Xb2"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="twx-NV-wpY"/>
                    </view>
                    <connections>
                        <outlet property="myView" destination="uKj-M5-owl" id="uCY-bV-QJd"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="q6f-6s-ke9" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53.600000000000001" y="101.19940029985008"/>
        </scene>
    </scenes>
</document>

и mySubView.xib:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="MySubView" customModule="SW4Temp" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="332" height="202"/>
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
                <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="T7q-1U-5iM">
                    <rect key="frame" x="106" y="20" width="120" height="30"/>
                    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                    <constraints>
                        <constraint firstAttribute="width" constant="120" id="WJn-fl-dH1"/>
                    </constraints>
                    <state key="normal" title="Button"/>
                    <connections>
                        <action selector="toggleView:" destination="iN0-l3-epB" eventType="touchUpInside" id="LSR-3h-g1f"/>
                    </connections>
                </button>
                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Vtd-O9-gRZ">
                    <rect key="frame" x="40" y="70" width="252" height="100"/>
                    <color key="backgroundColor" red="0.46202266219999999" green="0.83828371759999998" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                    <constraints>
                        <constraint firstAttribute="height" constant="100" id="e3B-MV-NZK"/>
                    </constraints>
                </view>
            </subviews>
            <color key="backgroundColor" red="0.83216959239999999" green="0.98548370600000001" blue="0.47333085539999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
            <constraints>
                <constraint firstAttribute="bottom" secondItem="Vtd-O9-gRZ" secondAttribute="bottom" priority="999" constant="20" id="0aE-RM-0AZ"/>
                <constraint firstItem="T7q-1U-5iM" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="20" id="Acg-yV-bn2"/>
                <constraint firstItem="Vtd-O9-gRZ" firstAttribute="top" secondItem="T7q-1U-5iM" secondAttribute="bottom" constant="20" id="KVh-lw-Sst"/>
                <constraint firstItem="T7q-1U-5iM" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="NUj-4y-fDg"/>
                <constraint firstItem="Vtd-O9-gRZ" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="40" id="cS4-7R-wW7"/>
                <constraint firstAttribute="trailing" secondItem="Vtd-O9-gRZ" secondAttribute="trailing" constant="40" id="kkG-9K-cEP"/>
            </constraints>
            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
            <connections>
                <outlet property="aView" destination="Vtd-O9-gRZ" id="SNl-ng-33p"/>
                <outlet property="aViewHeightConstraint" destination="e3B-MV-NZK" id="S4R-ct-gzE"/>
            </connections>
            <point key="canvasLocation" x="12" y="-179"/>
        </view>
    </objects>
</document>
...