如何用SwiftUI制作特定圆角样式的UI卡片组件?
SwiftUI 实现特定圆角UI卡片组件的标准方案
我在Pinterest上看到很多酷炫的UI卡片设计,想学习如何用SwiftUI制作这类卡片组件。尝试编写代码实现特定圆角的图片裁剪,但效果未达预期,附上现有代码,寻求标准实现方法。
现有尝试代码
import SwiftUI struct RoundedCorners: Shape { var radius: CGFloat = 25.0 var corners: UIRectCorner = .allCorners func path(in rect: CGRect) -> Path { let path = UIBezierPath( roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius) ) return Path(path.cgPath) } } struct ContentView: View { var body: some View { Image("images") .resizable() .aspectRatio(contentMode: .fill) .frame(width: 200, height: 200) .clipShape(RoundedCorners(radius: 50, corners: [.topLeft, .bottomRight])) .shadow(radius: 5) .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
方案1:用SwiftUI内置Modifier(推荐,iOS 15+)
从iOS 15开始,SwiftUI原生支持指定圆角的角落,无需自定义Shape,代码更简洁易维护:
import SwiftUI struct ContentView: View { var body: some View { // 完整Pinterest风格卡片示例,包含图片+文字内容 VStack(alignment: .leading, spacing: 12) { // 带特定圆角的图片区域 Image("images") .resizable() .aspectRatio(contentMode: .fill) .frame(height: 180) // 直接指定要圆角的角落:左上+右下 .cornerRadius(24, corners: [.topLeft, .bottomRight]) // 卡片文字内容区 VStack(alignment: .leading, spacing: 6) { Text("潮流穿搭指南") .font(.headline) .fontWeight(.semibold) Text("分享最新街头穿搭技巧,打造专属风格") .font(.subheadline) .foregroundColor(.secondary) } .padding(.horizontal, 16) .padding(.bottom, 16) } .frame(width: 280) .background(Color.white) // 给整个卡片统一加相同圆角,避免内容溢出 .cornerRadius(24, corners: [.topLeft, .bottomRight]) // 模拟Pinterest的柔和阴影 .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) .padding() .background(Color(.systemGray6)) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
方案2:兼容旧版本的自定义Shape优化
如果需要支持iOS 14及以下,可以优化你的自定义Shape,添加动画支持并封装成Modifier方便复用:
import SwiftUI struct RoundedCorners: Shape { var radius: CGFloat var corners: UIRectCorner func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) } // 添加动画支持,让圆角变化更流畅 var animatableData: AnimatablePair<CGFloat, UIRectCorner.AnimatableData> { get { AnimatablePair(radius, corners.animatableData) } set { radius = newValue.first corners.animatableData = newValue.second } } } // 封装成View扩展,调用更简洁 extension View { func roundedCorners(radius: CGFloat, corners: UIRectCorner) -> some View { clipShape(RoundedCorners(radius: radius, corners: corners)) } } struct ContentView: View { var body: some View { Image("images") .resizable() .aspectRatio(contentMode: .fill) .frame(width: 200, height: 200) .roundedCorners(radius: 50, corners: [.topLeft, .bottomRight]) // 调整阴影参数让效果更自然 .shadow(color: .black.opacity(0.15), radius: 5, x: 0, y: 2) .padding() } }
核心注意事项
- 使用
.aspectRatio(contentMode: .fill)时,必须配合.frame固定尺寸,防止图片拉伸变形 - 完整卡片建议用容器(如VStack)包裹所有元素,统一设置圆角和阴影,避免内容超出裁剪范围
- 阴影可以通过调整
opacity、radius和偏移量,模拟Pinterest卡片的柔和悬浮效果
内容的提问来源于stack exchange,提问作者BlurryFace
相关产品推荐
相关产品推荐

