You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift自定义UIView在EasyTipView内无法正常显示的问题求助

问题描述

我是iOS Swift开发新手,尝试用EasyTipView(一款Swift提示框库)展示自定义UIView,但该自定义视图无法被正确包含在EasyTipView内。同时我用视图控制器管理该自定义视图的生命周期,希望得到解决方向的指点。为简化问题,已移除自定义视图中的其他文本和按钮字段,相关代码如下:

自定义视图代码
import Foundation
import UIKit

public class ToolTipTemplate: UIView {
    private let templateAttributes: TemplateAttributes
    private var toolTipView: EasyTipView? = nil
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    init(templateAttributes: TemplateAttributes) {
        self.templateAttributes = templateAttributes
        super.init(frame: .zero)
        layoutView()
    }
    
    func layoutView() {
        let content: Tooltip = Tooltip()
        content.configureValues(text: toolTipTemplateAttributes.byline)
        addSubview(content)
        content.useAutolayout()
        NSLayoutConstraint.activate([
            content.centerYAnchor.constraint(equalTo: centerYAnchor),
            content.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
            content.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
            content.widthAnchor.constraint(equalTo: widthAnchor),
            content.heightAnchor.constraint(equalTo: heightAnchor)
        ])
    }
    
    override public func didMoveToSuperview() {
        if superview != nil {
          // 业务逻辑代码
        }
    }

}

extension ToolTipTemplate {
    
    open class Tooltip: UIView {
        private let messageLabel = UILabel()
        private let contentStack = UIStackView()
        
        private var messageString: String? {
            didSet {
                guard oldValue != messageString else { return }
                guard let messageString = messageString,
                      messageString.isEmpty == false else {
                    messageLabel.isHidden = true
                    return
                }
                
                messageLabel.attributedText = NSAttributedString(
                    string: messageString,
                    style: .secondary,
                    alignment: .center,
                    foregroundColor: .color(.primary),
                    lineBreakMode: .byWordWrapping
                )
                
                messageLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
                messageLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
                messageLabel.numberOfLines = 0
                messageLabel.isHidden = false
                messageLabel.isUserInteractionEnabled = false
            }
        }
        
        public init() {
            super.init(frame: CGRect.zero)
            setUpContent()
        }
        
        required public init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        public func configureValues(text: String? = nil) {
            messageString = text
            setNeedsUpdateConstraints()
        }
        
        override public func updateConstraints() {
            setupConstraints()
            super.updateConstraints()
        }
        
        func setUpContent() {
            useAutolayout()
            messageLabel.useAutolayout()
            
            // 配置内容栈
            contentStack.axis = .horizontal
            contentStack.spacing = 0.0
            contentStack.alignment = .center
            contentStack.distribution = .fill
            contentStack.useAutolayout()
            
            contentStack.addArrangedSubviews(messageLabel)
            contentStack.setCustomSpacing(.spacer(.small), after: messageLabel)
            
            messageLabel.isHidden = true
        }
        
        func setupConstraints() {
            removeAllSubviews()
            
            // 设置视图
            addSubview(contentStack)
            messageLabel.textAlignment = .natural
            
            NSLayoutConstraint.activate([
                // 内容视图约束
                contentStack.centerYAnchor.constraint(equalTo: centerYAnchor),
                messageLabel.topAnchor.constraint(equalTo: contentStack.topAnchor),
                messageLabel.leadingAnchor.constraint(equalTo: contentStack.leadingAnchor),
                messageLabel.trailingAnchor.constraint(equalTo: contentStack.trailingAnchor),
                
                contentStack.leadingAnchor.constraint(equalTo: leadingAnchor),
                contentStack.trailingAnchor.constraint(equalTo: trailingAnchor)
            ])
        }
    }
}
视图控制器代码
import Foundation

public class ToolTipViewController : UIViewController {

    private let tooltipMessageView: TooltipMessageView
    private let forView: UIView
    private let withinSuperview: UIView
    private var toolTipRef: EasyTipView? = nil

    init(tooltipMessageView: TooltipMessageView, forView: UIView, withinSuperview: UIView) {
        self.tooltipMessageView = tooltipMessageView
        self.forView = forView
        self.withinSuperview = withinSuperview
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func viewDidLoad() {
        super.viewDidLoad()
        showTooltip()
        view = toolTipRef
    }

    func showTooltip() {
        guard let toolTip = tooltipMessageView.getView() as? ToolTipTemplate else {
            return
        }
        
        toolTipRef = EasyTipView.standard(contentView: toolTip, arrowPosition: .bottom, boundsSize: withinSuperview.bounds.size, dismissOnTap: false, delegate: self)
        toolTipRef?.show(forView: forView, withinSuperview: withinSuperview) {
            [weak self] (finished) in
            guard finished == true else { return }
            // 业务逻辑代码
        }
    }
}

extension ToolTipViewController: EasyTipViewDelegate {
    public func easyTipViewDidDismiss(_ tipView: EasyTipView) {
        // 业务逻辑代码
        toolTipRef = nil
    }
}
解决方向
  • 约束修复

    • ToolTipTemplate中content视图的约束使用了safeAreaLayoutGuide,但EasyTipView作为父视图不存在安全区域,应改为直接绑定到自身的锚点:
      content.leadingAnchor.constraint(equalTo: leadingAnchor),
      content.trailingAnchor.constraint(equalTo: trailingAnchor),
      
    • Tooltip类的setupConstraints方法中每次更新约束都会调用removeAllSubviews(),这会导致子视图被反复移除重建,破坏布局,直接删除该行代码即可。
    • 确保自定义视图能通过自动布局计算出明确尺寸:给messageLabel设置合理的内容压缩/拉伸优先级,或给ToolTipTemplate添加固定宽高约束(如果需要)。
  • EasyTipView使用修正

    • 删除ToolTipViewController的viewDidLoad中view = toolTipRef这行代码。EasyTipView通过show方法已经完成视图添加和显示,无需将其设为控制器根视图,否则会导致层级混乱。
    • 初始化EasyTipView时可移除boundsSize参数,让库自动根据内容视图尺寸计算提示框大小,避免手动传入的尺寸与实际内容不匹配。
  • 生命周期管理优化

    • 若需用视图控制器管理自定义视图生命周期,应将自定义视图添加到控制器的view上,再把控制器的view作为EasyTipView的contentView,确保控制器生命周期方法正常触发:
      // 在showTooltip方法中修改
      guard let toolTip = tooltipMessageView.getView() as? ToolTipTemplate else { return }
      view.addSubview(toolTip)
      // 让toolTip充满控制器view
      toolTip.translatesAutoresizingMaskIntoConstraints = false
      NSLayoutConstraint.activate([
          toolTip.topAnchor.constraint(equalTo: view.topAnchor),
          toolTip.leadingAnchor.constraint(equalTo: view.leadingAnchor),
          toolTip.trailingAnchor.constraint(equalTo: view.trailingAnchor),
          toolTip.bottomAnchor.constraint(equalTo: view.bottomAnchor)
      ])
      toolTipRef = EasyTipView.standard(contentView: self.view, arrowPosition: .bottom, dismissOnTap: false, delegate: self)
      

内容的提问来源于stack exchange,提问作者Sabya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:42:03