自定义 UITableViewCell、UISlider 子视图意外的大小调整行为

Custom UITableViewCell, UISlider subview unexpected sizing behavior

提问人:psb 提问时间:10/26/2023 更新时间:11/2/2023 访问量:54

问:

我在自定义UITableViewCell中有一个UISlider。 当我查看属性中滑块的大小时,显示的是滑块在情节提要中设置的大小,而不是视图出现时绘制的最终大小。awakeFromNib.frame

我以为所有这些设置都已经完成,但滑块的大小似乎在 awakeFromNib 和它的最终外观之间发生了变化。awakeFromNib

我在 2015 年发现了一个类似的问题,该问题已经发布了答案,但实际上并没有得到解决。

UITableViewCell:了解生命周期

我在 2016 年也发现了一个类似的问题,但这个问题似乎不适用于我的情况。

Swift UITableViewCell 子视图布局更新延迟

我添加了情节提要中设置的约束的屏幕截图。

UISlider constraints

Swift UITableView UIKiti AwakeFromnib

评论

0赞 son 10/26/2023
那么目前的问题是什么呢?宽度/高度滑块是否不正确?
0赞 psb 10/26/2023
宽度不正确。当我在 awakeFromNib 中查看 .frame.width 时,它显示的是情节提要中设置的值,而不是显示后的宽度。所以我遇到的问题是我正在根据滑块的大小计算其他界面元素的位置。因此,在视图显示后,随着滑块大小的更改,其他元素也会发生变化。
0赞 son 10/26/2023
您是否在情节提要中的同一设备或模拟器上运行?具体来说,在这种情况下,iPhone 15 Pro。你能展示这个问题的实际截图吗?
0赞 psb 10/26/2023
当我在 iPhone 15 模拟器上运行它时,滑块的大小是正确的(如故事板中设置的那样)。在 iPhone 15 Pro 上运行时,awakeFromNib 中的大小显示为故事板中设置的大小,然后,当视图显示时,滑块大小看起来正确,宽度读为 iPhone 15 Pro 正确。因此,我认为显示屏幕截图没有帮助,因为在显示视图时,滑块的大小是正确的。
1赞 son 10/26/2023
确实如此,因为您使用的是自动布局,这意味着可以使用其 superView 更改布局。在 or 中,它只是获取接口文件(来自 .xib 或情节提要文件),然后使用 superView 进行调整。您可以在 viewDidLoad/awakeFromNib 和 viewDidLayoutSubviews/layoutSubviews 上打印出,以查看不同的viewDidLoadawakeFromNib

答:

1赞 DonMag 10/30/2023 #1

直到单元格(及其 UI 组件)的大小,我们才知道layoutSubviews()

因此,假设您将箭头位置设置为百分比,请在单元格类中按照以下行实现:layoutSubviews()

override func layoutSubviews() {
    super.layoutSubviews()
    
    // the thumb "circle" extends to the bounds / frame of the slider
    // so, this is how we get the
    //  thumb center-to-center
    //  when value is 0 or 1.0
    let trackRect = theSlider.trackRect(forBounds: theSlider.bounds)
    let thumbRect = theSlider.thumbRect(forBounds: theSlider.bounds, trackRect: trackRect, value: 0.0)
    let rangeWidth = theSlider.bounds.width - thumbRect.width
    
    // Zero will be 1/2 of the width of the thumbRect
    //  minus 2 (because the thumb image is slightly offset from the thumb rect)
    let xOffset = (thumbRect.width * 0.5) - 2.0
    
    // create the arrow constraints if needed
    if startConstraint == nil {
        startConstraint = startArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
        startConstraint.isActive = true
    }
    if endConstraint == nil {
        endConstraint = endArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
        endConstraint.isActive = true
    }
    
    // set arrow constraint constants
    startConstraint.constant = rangeWidth * startTime + xOffset
    endConstraint.constant = rangeWidth * endTime + xOffset
}

我假设您的所有行都将具有相同的滑块“时间范围”,因此我们可以得到这样的结果(我将拇指色调设置为半透明,将箭头 y 位置设置为对齐方式,以便我们可以看到对齐方式):

enter image description here

对于完整的示例(要生成该输出),请使用此 Storyboard https://pastebin.com/nUZFMtGN(由于此答案变得太长而不得不移动它)和以下代码:

class SliderCell: UITableViewCell {
    // startTime and endTime are in Percentages
    public var startTime: Double = 0.0 { didSet { setNeedsLayout() } }
    public var endTime: Double = 0.0 { didSet { setNeedsLayout() } }
    
    @IBOutlet var startArrow: UIImageView!
    @IBOutlet var endArrow: UIImageView!

    @IBOutlet var dateLabel: UILabel!
    @IBOutlet var startEndLabel: UILabel!
    
    @IBOutlet var minLabel: UILabel!
    @IBOutlet var maxLabel: UILabel!
    
    @IBOutlet var theSlider: UISlider!
    
    private var startConstraint: NSLayoutConstraint!
    private var endConstraint: NSLayoutConstraint!
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // the thumb "circle" extends to the bounds / frame of the slider
        // so, this is how we get the
        //  thumb center-to-center
        //  when value is 0 or 1.0
        let trackRect = theSlider.trackRect(forBounds: theSlider.bounds)
        let thumbRect = theSlider.thumbRect(forBounds: theSlider.bounds, trackRect: trackRect, value: 0.0)
        let rangeWidth = theSlider.bounds.width - thumbRect.width
        
        // Zero will be 1/2 of the width of the thumbRect
        //  minus 2 (because the thumb image is slightly offset from the thumb rect)
        let xOffset = (thumbRect.width * 0.5) - 2.0
        
        // create the arrow constraints if needed
        if startConstraint == nil {
            startConstraint = startArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
            startConstraint.isActive = true
        }
        if endConstraint == nil {
            endConstraint = endArrow.centerXAnchor.constraint(equalTo: theSlider.leadingAnchor)
            endConstraint.isActive = true
        }
        
        // set arrow constraint constants
        startConstraint.constant = rangeWidth * startTime + xOffset
        endConstraint.constant = rangeWidth * endTime + xOffset
    }
}

struct MyTimeInfo {
    var startTime: Date = Date()
    var endTime: Date = Date()
}

class SliderTableVC: UITableViewController {
    
    var myData: [MyTimeInfo] = []
    
    var minTime: Double = 0
    var maxTime: Double = 24

    var minTimeStr: String = ""
    var maxTimeStr: String = ""
    
    var timeRange: Double = 24
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // let's generate some sample data
        let starts: [Double] = [
            8, 7, 11, 10.5, 8.25, 9,
        ]
        let ends: [Double] = [
            20, 23, 19, 16.5, 21.75, 21,
        ]
        let y = 2023
        let m = 11
        var d = 1
        for (s, e) in zip(starts, ends) {
            var dateComponents = DateComponents()
            dateComponents.year = y
            dateComponents.month = m
            dateComponents.day = d
            dateComponents.hour = Int(s)
            dateComponents.minute = Int((s - Double(Int(s))) * 60.0)
            let sDate = Calendar.current.date(from: dateComponents)!
            dateComponents.hour = Int(e)
            dateComponents.minute = Int((e - Double(Int(e))) * 60.0)
            let eDate = Calendar.current.date(from: dateComponents)!
            myData.append(MyTimeInfo(startTime: sDate, endTime: eDate))
            d += 1
        }
        
        minTime = starts.min() ?? 0
        maxTime = ends.max() ?? 24
        timeRange = maxTime - minTime
        
        minTimeStr = timeStringFromDouble(minTime)
        maxTimeStr = timeStringFromDouble(maxTime)
        
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let c = tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderCell
        
        let calendar = Calendar.current

        var h = calendar.component(.hour, from: myData[indexPath.row].startTime)
        var m = calendar.component(.minute, from: myData[indexPath.row].startTime)

        let s: Double = Double(h) + Double(m) / 60.0

        h = calendar.component(.hour, from: myData[indexPath.row].endTime)
        m = calendar.component(.minute, from: myData[indexPath.row].endTime)
        
        let e: Double = Double(h) + Double(m) / 60.0
        
        let sPct: Double = (s - minTime) / timeRange
        let ePct: Double = (e - minTime) / timeRange
        
        let df = DateFormatter()
        df.timeStyle = .short

        let sStr = df.string(from: myData[indexPath.row].startTime)
        let eStr = df.string(from: myData[indexPath.row].endTime)

        df.dateStyle = .short
        df.timeStyle = .none
        
        c.dateLabel.text = df.string(from: myData[indexPath.row].startTime)
        
        c.startTime = max(sPct, 0.0)
        c.endTime = min(ePct, 1.0)
        
        c.startEndLabel.text = sStr + " - " + eStr
        
        c.minLabel.text = minTimeStr
        c.maxLabel.text = maxTimeStr
        
        return c
        
    }
    func timeStringFromDouble(_ t: Double) -> String {
        
        let df = DateFormatter()
        df.timeStyle = .short
        
        var dateComponents = DateComponents()
        dateComponents.hour = Int(t)
        dateComponents.minute = Int((t - Double(Int(t))) * 60.0)
        
        var date = Calendar.current.date(from: dateComponents)!
        return df.string(from: date)

    }
}

编辑

如果我们愿意,我们可以完全摆脱位置计算......layoutSubviews()

让我们从一个自定义滑块拇指图像开始,我们可以在运行时使用 SF 符号生成它 - 背景会很清晰:

enter image description here enter image description here

如果我们将它与 一起使用,它将看起来像这样(为了清楚起见,我给它一个半透明的背景):.setThumbImage(arrowThumb, for: [])

enter image description here

例如,现在我们可以设置滑块的:

.minimumValue = 7.0   // (hours - 7:00 am)
.maximumValue = 23.0  // (hours - 11:00 pm)

,然后将该值设置为时间。

因此,我们可以将一个用于“开始时间”,并覆盖另一个用于“结束时间”:

enter image description here

如果我们然后设置:

.setMinimumTrackImage(UIImage(), for: [])
.setMaximumTrackImage(UIImage(), for: [])

在两个滑块上,我们得到这个:

enter image description here

我们将为这两个滑块进行设置,并在顶部覆盖一个交互式滑块:.isUserInteractionEnabled = false

enter image description here

调试视图层次结构:

enter image description here

当我们删除半透明背景时:

enter image description here

在这一点上,我们不再需要做任何事情......我们只需设置“startMarkerSlider”和“endMarkerSlider”,箭头标记就会自动定位。layoutSubviews().value

enter image description here

下面是该方法的示例代码 - 所有代码、no 或 connections...@IBOutlet@IBAction

// convenience extension to manage Date Times as fractions
// for example
//  convert from to 10:15 to 10.25
// and
//  convert from 10.25 to 10:15
extension Date {
    var fractionalTime: Double {
        get {
            let calendar = Calendar.current
            let h = calendar.component(.hour, from: self)
            let m = calendar.component(.minute, from: self)
            return Double(h) + Double(m) / 60.0
        }
        set {
            let calendar = Calendar.current
            var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)
            components.hour = Int(newValue)
            components.minute = Int(newValue * 60.0) % 60
            self = calendar.date(from: components)!
        }
    }
}

Table View Cell 类

class AnotherSliderCell: UITableViewCell {
    
    public var sliderClosure: ((UITableViewCell, Double) -> ())?
    
    private var minTime: Date = Date() { didSet {
        startMarkerSlider.minimumValue = Float(minTime.fractionalTime)
        endMarkerSlider.minimumValue = startMarkerSlider.minimumValue
        theSlider.minimumValue = startMarkerSlider.minimumValue
    }}
    private var maxTime: Date = Date() { didSet {
        startMarkerSlider.maximumValue = Float(maxTime.fractionalTime)
        endMarkerSlider.maximumValue = startMarkerSlider.maximumValue
        theSlider.maximumValue = startMarkerSlider.maximumValue
    }}
    private var startTime: Date = Date() { didSet {
        startMarkerSlider.setValue(Float(startTime.fractionalTime), animated: false)
    }}
    private var endTime: Date = Date() { didSet {
        endMarkerSlider.setValue(Float(endTime.fractionalTime), animated: false)
    }}
    private var selectedTime: Date = Date() { didSet {
        theSlider.setValue(Float(selectedTime.fractionalTime), animated: false)
    }}

    private let theSlider = UISlider()
    private let startMarkerSlider = UISlider()
    private let endMarkerSlider = UISlider()
    
    private let infoLabel = UILabel()
    private let minLabel = UILabel()
    private let maxLabel = UILabel()
    private let selLabel = UILabel()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        
        [endMarkerSlider, startMarkerSlider, theSlider, infoLabel, minLabel, maxLabel, selLabel].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(v)
        }
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            
            infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            
            infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),

            theSlider.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 6.0),
            theSlider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            theSlider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            
            minLabel.topAnchor.constraint(equalTo: theSlider.bottomAnchor, constant: 8.0),
            minLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            minLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),

            maxLabel.topAnchor.constraint(equalTo: minLabel.topAnchor, constant: 0.0),
            maxLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            
            maxLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
            
            selLabel.topAnchor.constraint(equalTo: minLabel.topAnchor, constant: 0.0),
            selLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
        ])
        
        // constrain sliders to overlay each other
        [endMarkerSlider, startMarkerSlider].forEach { v in
            NSLayoutConstraint.activate([
                v.topAnchor.constraint(equalTo: theSlider.topAnchor, constant: 0.0),
                v.leadingAnchor.constraint(equalTo: theSlider.leadingAnchor, constant: 0.0),
                v.trailingAnchor.constraint(equalTo: theSlider.trailingAnchor, constant: 0.0),
                v.bottomAnchor.constraint(equalTo: theSlider.bottomAnchor, constant: 0.0),
            ])
        }

        var arrowThumb: UIImage!

        // if we can get the "arrowshape.up" SF Symbol (iOS 17 or custom), use it
        //  else
        // if we can get the "arrowshape.left" SF Symbol, rotate and use it
        //  else
        // use a bezier path to draw the arrow
        if let sfArrow = UIImage(systemName: "arrowshape.up") {
            
            let newSize: CGSize = .init(width: 31.0, height: (sfArrow.size.height * 2.0) + 3.0)
            let xOff = (newSize.width - sfArrow.size.width) * 0.5
            let yOff = (newSize.height - sfArrow.size.height)
            
            arrowThumb = UIGraphicsImageRenderer(size:newSize).image { renderer in
                // during development, if we want to see the thumb image framing
                //UIColor.red.withAlphaComponent(0.25).setFill()
                //renderer.cgContext.fill(CGRect(origin: .zero, size: newSize))
                sfArrow.draw(at: .init(x: xOff, y: yOff))
            }
            
        } else if let sfArrow = UIImage(systemName: "arrowshape.left") {
            
            let sizeOfImage = sfArrow.size
            var newSize = CGRect(origin: .zero, size: sizeOfImage).applying(CGAffineTransform(rotationAngle: .pi * 0.5)).size
            
            // Trim off the extremely small float value to prevent core graphics from rounding it up
            newSize.width = floor(newSize.width)
            newSize.height = floor(newSize.height)
            
            let rotArrow = UIGraphicsImageRenderer(size:newSize).image { renderer in
                //rotate from center
                renderer.cgContext.translateBy(x: newSize.width/2, y: newSize.height/2)
                renderer.cgContext.rotate(by: .pi * 0.5)
                sfArrow.draw(at: .init(x: -newSize.height / 2, y: -newSize.width / 2))
            }
            
            newSize = .init(width: 31.0, height: (rotArrow.size.height * 2.0) + 3.0)
            var xOff: CGFloat = (newSize.width - rotArrow.size.width) * 0.5
            var yOff: CGFloat = newSize.height - rotArrow.size.height
            
            arrowThumb = UIGraphicsImageRenderer(size:newSize).image { renderer in
                // during development, if we want to see the thumb image framing
                //UIColor.red.withAlphaComponent(0.25).setFill()
                //renderer.cgContext.fill(CGRect(origin: .zero, size: newSize))
                rotArrow.draw(at: .init(x: xOff, y: yOff))
            }
            
        } else {

            let vr: CGRect = .init(x: 0.0, y: 0.0, width: 31.0, height: 40.0)
            let r: CGRect = .init(x: 6.5, y: 23.0, width: 18.0, height: 16.0)
            
            var pt: CGPoint = .zero
            let pth = UIBezierPath()
            
            pt.x = r.midX - 3.0
            pt.y = r.maxY
            pth.move(to: pt)
            pt.y = r.maxY - 8.0
            pth.addLine(to: pt)
            pt.x = r.minX
            pth.addLine(to: pt)
            pt.x = r.midX
            pt.y = r.minY
            pth.addLine(to: pt)
            pt.x = r.maxX
            pt.y = r.maxY - 8.0
            pth.addLine(to: pt)
            pt.x = r.midX + 3.0
            pth.addLine(to: pt)
            pt.y = r.maxY
            pth.addLine(to: pt)
            pth.close()
            
            arrowThumb = UIGraphicsImageRenderer(size: vr.size).image { ctx in
                
                ctx.cgContext.setStrokeColor(UIColor.red.cgColor)
                
                ctx.cgContext.setLineWidth(1)
                ctx.cgContext.setLineJoin(.round)
                
                ctx.cgContext.addPath(pth.cgPath)
                ctx.cgContext.drawPath(using: .stroke)
                
            }
            
        }
        
        [endMarkerSlider, startMarkerSlider].forEach { v in
            v.setThumbImage(arrowThumb, for: [])
            v.setMinimumTrackImage(UIImage(), for: [])
            v.setMaximumTrackImage(UIImage(), for: [])
            v.isUserInteractionEnabled = false
        }

        infoLabel.font = .systemFont(ofSize: 16.0, weight: .regular)
        infoLabel.textAlignment = .center
        infoLabel.numberOfLines = 0
        
        minLabel.font = .systemFont(ofSize: 12.0, weight: .light)
        maxLabel.font = minLabel.font

        selLabel.font = minLabel.font
        selLabel.textColor = .systemRed

        theSlider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
        
        theSlider.thumbTintColor = .green.withAlphaComponent(0.25)
    }
    
    @objc func sliderChanged(_ sender: UISlider) {
        let df = DateFormatter()
        
        df.dateStyle = .none
        df.timeStyle = .short
        
        var dt = Date()
        dt.fractionalTime = Double(sender.value)
        selLabel.text = "Thumb Time: " + df.string(from: dt)

        sliderClosure?(self, Double(sender.value))
    }
    
    public func fillData(minTime: Date, maxTime: Date, mti: MyTimeInfo) {
        let df = DateFormatter()
        
        df.dateStyle = .full
        df.timeStyle = .none
        
        let part1: String = df.string(from: mti.startTime)

        df.dateStyle = .none
        df.timeStyle = .short
        
        let startStr: String = df.string(from: mti.startTime)
        let endStr: String   = df.string(from: mti.endTime)
        let selStr: String   = df.string(from: mti.selectedTime)

        let minStr: String = df.string(from: minTime)
        let maxStr: String = df.string(from: maxTime)

        infoLabel.text = part1 + "\n" + "Marker Times" + "\n" + startStr + " - " + endStr
        minLabel.text = minStr
        maxLabel.text = maxStr
        selLabel.text = "Thumb Time: " + selStr
        
        self.minTime = minTime
        self.maxTime = maxTime
        self.startTime = mti.startTime
        self.endTime = mti.endTime
        self.selectedTime = mti.selectedTime
    }
    
    // we don't need layoutSubviews() anymore
    //override func layoutSubviews() {
    //  super.layoutSubviews()
    //}
    
}

示例控制器类

class AnotherSliderTableVC: UITableViewController {
    
    var myData: [MyTimeInfo] = []
    
    var minTime: Date = Date()
    var maxTime: Date = Date()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        // let's generate some sample data
        let samples: [[String]] = [
            ["11/2/2023 9:00 AM", "11/2/2023 9:00 PM"],
            ["11/2/2023 9:00 AM", "11/2/2023 5:00 PM"],
            ["11/3/2023 10:00 AM", "11/3/2023 5:00 PM"],
            ["11/4/2023 10:20 AM", "11/4/2023 2:45 PM"],
            ["11/5/2023 9:15 AM", "11/5/2023 9:30 PM"],
            ["11/6/2023 11:00 AM", "11/6/2023 6:00 PM"],
            ["11/7/2023 11:45 AM", "11/7/2023 7:30 PM"],
            ["11/8/2023 10:45 AM", "11/8/2023 4:00 PM"],
            ["11/9/2023 8:35 AM", "11/9/2023 9:00 PM"],
        ]
        
        let df = DateFormatter()
        df.dateFormat = "MM/dd/yyyy h:mm a"
        
        samples.forEach { ss in
            if let st = df.date(from: ss[0]),
               let et = df.date(from: ss[1]) {
                var selt = st
                // init with 12:00 as selectedTime for all samples
                selt.fractionalTime = 12.0
                let mt = MyTimeInfo(startTime: st, endTime: et, selectedTime: selt)
                myData.append(mt)
            }
        }
        
        // let's use these min/max times for the sliders
        //  the Date will be ignored ... only the Time will be used
        var sTmp = "11/2/2023 7:00 AM"
        if let d = df.date(from: sTmp) {
            minTime = d
        }
        sTmp = "11/2/2023 11:00 PM"
        if let d = df.date(from: sTmp) {
            maxTime = d
        }
        
        tableView.register(AnotherSliderCell.self, forCellReuseIdentifier: "ac")
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cc = tableView.dequeueReusableCell(withIdentifier: "ac", for: indexPath) as! AnotherSliderCell
        cc.fillData(minTime: minTime, maxTime: maxTime, mti: myData[indexPath.row])
        cc.sliderClosure = { [weak self] theCell, theValue in
            guard let self = self,
                  let idx = tableView.indexPath(for: theCell)
            else { return }
            self.myData[idx.row].selectedTime.fractionalTime = theValue
        }
        return cc
    }
    func timeStringFromDouble(_ t: Double) -> String {
        
        let df = DateFormatter()
        df.timeStyle = .short
        
        var dateComponents = DateComponents()
        dateComponents.hour = Int(t)
        dateComponents.minute = Int((t - Double(Int(t))) * 60.0)
        
        let date = Calendar.current.date(from: dateComponents)!
        return df.string(from: date)
        
    }
}

请注意,我还更改了使用数据的方法,因此我们直接处理对象。Date

评论

0赞 psb 10/31/2023
我非常感谢您详细而彻底的回答!我有点解决了我的问题,就像我确实找到了一个解决方案一样(尽管它不如你的强大,而且在视图出现时还包括一些视觉上的“打嗝”。我一定会梳理你的方法并修改我的解决方案。谢谢!
0赞 psb 10/31/2023
由于我的应用程序的一些功能更改,我重新设计了它,以便删除情节提要中的标签,而是以编程方式添加它们(以及其他视觉元素)。您的解决方案使我明白,我需要将大部分子视图位置计算移动到 layoutSubviews() (...必要时调用 setNeedsLayout()。现在一切都像我预期的那样工作。再次,非常感谢。
1赞 DonMag 11/2/2023
@psb - 如果您有兴趣,我使用另一种我认为更容易管理的方法在我的答案中添加了编辑