StackView内SF-Symbols变形,复刻Apple Map按钮及添加分割线问题
Hey there! Let's tackle those two issues with your custom Apple Maps-style buttons one by one. I'll walk you through the fixes step by step, with updated code and explanations.
1. Fix SF Symbols Distortion
The main culprits behind the symbol stretching are:
- Using the initial
framefor rendering instead of adapting to dynamic view size changes - Manually drawing the image with fixed insets that don't scale with the view
- Using
UIImageViewdirectly as the container without proper layout control
Let's refactor the CustomView to use a container UIView with an internal UIImageView for the symbol. This gives us far better control over layout and scaling:
class CustomView: UIView { init(frame: CGRect, corners: CACornerMask, systemName: String) { super.init(frame: frame) setupView(corners: corners) setupSymbolImageView(systemName: systemName) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupView(corners: CACornerMask) { backgroundColor = .white layer.cornerRadius = 20 layer.maskedCorners = corners layer.shadowOffset = .zero layer.shadowColor = UIColor.gray.cgColor layer.shadowRadius = 20 layer.shadowOpacity = 0.2 // Fix shadow path to use bounds (updates with view size changes) layer.shadowPath = UIBezierPath(rect: bounds.insetBy(dx: -1, dy: 2)).cgPath layer.masksToBounds = false } private func setupSymbolImageView(systemName: String) { let symbolImageView = UIImageView() symbolImageView.translatesAutoresizingMaskIntoConstraints = false symbolImageView.contentMode = .scaleAspectFit // Ensures symbol scales without stretching symbolImageView.tintColor = .black // Customize this to match your desired color symbolImageView.image = UIImage(systemName: systemName)?.withRenderingMode(.alwaysTemplate) addSubview(symbolImageView) // AutoLayout constraints to center the symbol with consistent padding NSLayoutConstraint.activate([ symbolImageView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 30), symbolImageView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -30), symbolImageView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 30), symbolImageView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -30), symbolImageView.centerXAnchor.constraint(equalTo: centerXAnchor), symbolImageView.centerYAnchor.constraint(equalTo: centerYAnchor), // Optional: Prevent symbol from over-scaling on larger views symbolImageView.widthAnchor.constraint(lessThanOrEqualToConstant: 40), symbolImageView.heightAnchor.constraint(lessThanOrEqualToConstant: 40) ]) } // Helper method to add bottom dividers (we'll use this next) func addBottomDivider() { let divider = UIView() divider.translatesAutoresizingMaskIntoConstraints = false divider.backgroundColor = UIColor.lightGray.withAlphaComponent(0.3) // Subtle divider color addSubview(divider) NSLayoutConstraint.activate([ divider.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), divider.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), divider.bottomAnchor.constraint(equalTo: bottomAnchor), divider.heightAnchor.constraint(equalToConstant: 1) ]) } }
2. Add Subtle Dividers Between StackView Items
To match Apple Maps' divider style, we'll add a thin line to the bottom of each CustomView (except the last one). Here's how to update your StackView setup code:
let size = CGSize(width: 100, height: 100) let frame = CGRect(origin: CGPoint(x: 100, y: 100), size: size) let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle") infoView.addBottomDivider() // Add divider below this view let locationView = CustomView(frame: frame, corners: [], systemName: "location") locationView.addBottomDivider() // Add divider below this view let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d") twoDView.addBottomDivider() // Add divider below this view let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill") // No divider on the last item let stackView = UIStackView(arrangedSubviews: [infoView, locationView, twoDView, binocularsView]) stackView.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 100, height: 400)) stackView.distribution = .fillEqually stackView.axis = .vertical stackView.setCustomSpacing(0, after: twoDView) // Remove extra spacing—dividers are part of the views now view.addSubview(stackView) // Optional: Replace fixed frames with AutoLayout for better adaptability stackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100), stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100), stackView.widthAnchor.constraint(equalToConstant: 100), stackView.heightAnchor.constraint(equalToConstant: 400) ])
Key Fixes Recap
- Symbol Distortion: Using an internal
UIImageViewwithscaleAspectFitand AutoLayout ensures the symbol scales proportionally, no matter how theCustomViewresizes in the StackView. The optional maximum size constraint prevents the symbol from becoming too large. - Dividers: The
addBottomDividermethod adds a subtle, semi-transparent line at the bottom of each view (except the last one), exactly matching Apple Maps' divider style. We also removed the extra spacing between views since dividers are now integrated into the button views themselves.
内容的提问来源于stack exchange,提问作者user12473575
相关产品推荐
相关产品推荐

