布局(二) – 叶子视图

温馨提示:本文最后更新于2024-10-23 18:29:51,某些文章具有时效性,若有错误或已失效,请在下方留言

叶子视图 这里是指没有子视图的视图。

Text

默认情况下,Text 视图会去适应任意的建议尺⼨,设法让⾃⼰适配 (不超过) 这个尺⼨。Text 使用的策略顺序如下:

  1. 将⽂本分成多⾏ (英⽂内容按单词换⾏)
  2. 单词内换⾏ (使⽤连词符号)
  3. 截断⽂本
  4. 裁剪⽂本

Text始终会返回它所需要渲染的内容的大小,这个尺寸在宽度上一定小于或等于建议的跨度,在高度上除非提议的是 0x0,否则至少是一行的高度。也就是说,Text的宽度可以从零到完整渲染内容所需的宽度之间的任意值。

Text(“Hello, World!”) 在不同建议尺⼨下的渲染示例。虚线矩形代表建议尺⼨,实线矩形代表报告的尺⼨:

Text ("Hello, World!") 在不同建议尺⼨的渲染
Text (“Hello, World!”) 在不同建议尺⼨的渲染


在 Text 上,我们可以使⽤⼀系列修饰符来改变它的⾏为:

  • .lineLimit(_ number:) 允许我们指定渲染时的最⼤⾏数,不论在建议尺⼨的垂直⽅向上空间⾜够与否,⽂本都不能超过这个⾏数。指定为 nil 代表没有⾏数限制。
  • .lineLimit(_ limit:reservesSpace:) 允许我们指定渲染时的最⼤⾏数,并且让我们有机会选择是否要在建议尺⼨⾜够的情况下,让额外的空格去将⾏数补充到这个最⼤⾏数。
  • .truncationMode(_ mode:) 允许我们指定⽂本截断应该发⽣的位置。
  • .minimumScaleFactor(_ factor:) 允许我们指定 Text 在需要缩⼩字体以适应建议尺⼨时,所能够使⽤的最⼩⽐例。

如果我们将 .fixedSize() 应⽤于 Text,它将使⽤理想尺⼨,因为 .fixedSize()Text 所建议的尺⼨是 nil⨉nilText 的理想尺⼨是在不换⾏和不截断的情况下呈

现内容所需的尺⼨。下⾯是 Text("Hello, World!”).fixedSize() 在不同建议尺⼨下的渲染情况:

Text("Hello, World!”).fixedSize() 在不同建议尺⼨下渲染
Text(“Hello, World!”).fixedSize() 在不同建议尺⼨下渲染


fixedSize 修饰符会确保⼦视图 (这⾥的 Text) 始终以其理想尺⼨进⾏渲染,⽽不考虑建议尺⼨。

决定视图布局的6 个值:

  1. 最小宽度和最小高度,用于存储该视图可接受的最小空间。任何小于这些值的内容都会被忽略,导致视图 “泄漏” 出建议给它的空间。
  2. 最大宽度和最大高度,用于存储视图可接受的最大空间。大于这些值的内容将被忽略,这意味着父视图必须在剩余空间内以某种方式定位视图。
  3. 理想宽度和理想高度,用于存储该视图所需的首选空间。只要这些值仍在最小值到最大值的范围内,就可以提供这些值之外的值。

形状

内置形状

⼤部分内置的形状类型 (RectangleRoundedRectangleCapsule,以及 Ellipse) 接受从零到⽆限的任意⼤⼩的建议尺⼨,并且会填充所有可⽤空间。Circle 是⼀个特例:它会按照建议尺⼨的短边作为直径进⾏适配,然后将圆形的实际尺⼨进⾏汇报。

如果我们使⽤ nil 来对形状进⾏尺⼨建议 (⽐如,我们把形状包装到 fixedSize ⾥),它将使⽤ 10⨉10 这个默认尺⼨。

HStack {
    Rectangle()
        .fill(.orange)
        .fixedSize()

    Circle()
        .fill(.red)
        .fixedSize()
    
    RoundedRectangle(cornerRadius: 4)
        .fill(.yellow)
        .fixedSize()
    
    Capsule()
        .fill(.yellow)
        .fixedSize()
}

形状使用 fixedSize 修饰符
形状使用 fixedSize 修饰符


自定义形状

自定义书签形状的代码,如下所示

extension CGRect {
    subscript(_ point: UnitPoint) -> CGPoint {
        CGPoint(x: minX + width * point.x, y: minY + height * point.y)
    }
}

struct BookmarkShape: Shape {
    nonisolated func path(in rect: CGRect) -> Path {
        Path { p in
            p.move(to: rect[.topLeading])
            p.addLines([
                rect[.bottomLeading],
                rect[.init(x: 0.5, y: 0.8)],
                rect[.bottomTrailing],
                rect[.topTrailing],
                rect[.topLeading]
            ])
            
            p.closeSubpath()
        }
    }
}

//struct BookmarkShape: Shape {
//    nonisolated func path(in rect: CGRect) -> Path {
//        Path { p in
//            p.move(to: CGPoint(x: rect.minX, y: rect.minY))
//            p.addLines([
//                CGPoint(x: rect.minX, y: rect.maxY),
//                CGPoint(x: rect.midX, y: rect.maxY * 0.8),
//                CGPoint(x: rect.maxX, y: rect.maxY),
//                CGPoint(x: rect.maxX, y: rect.minY),
//                CGPoint(x: rect.minX, y: rect.minY)
//            ])
//            
//            p.closeSubpath()
//        }
//    }
//}

struct Bookmark: View {
    var body: some View {
        VStack {
            BookmarkShape()
                .stroke(.red.gradient)
        }
        .padding()
    }
}

自定义的形状也会填充所有可用的空间。

自定义书签形状
自定义书签形状


假设这个形状应该始终以 2⨉3 的宽⾼⽐进⾏渲染,可以在使⽤这个形状的地⽅添加上.aspectRatio 修饰符,也可以覆盖原有的 sizeThatFits 方法,返回需要的提议尺寸。

nonisolated func sizeThatFits(_ proposal: ProposedViewSize) -> CGSize {
	var result = proposal.replacingUnspecifiedDimensions()
    let ratio: CGFloat = 2 / 3
    let newWidth = ratio * result.height
    
    if newWidth <= result.width {
        result.width = newWidth
    } else {
        result.height = result.width / ratio
    }
    
    return result
}

设置宽高比 2 / 3
设置宽高比 2 / 3


建议尺⼨的⾥的属性可能是 nil,因此我们⾸先要调⽤ replacingUnspeci!edDimensions 来获取⼀个 CGSize。然后,我们计算形状的宽度和⾼度,使其宽⾼⽐为 2×3,并适配到建议尺⼨中去。

自定义形状在不同建议尺寸下的渲染
自定义形状在不同建议尺寸下的渲染


颜色

当直接将颜⾊当作视图使⽤时,⽐如 Color.red,从视图布局的视⻆来看,它的⾏为和 Rectangle().fill(.red) 是⼀样的。

Color.red与 Rectangle().fill(.red)的效果
Color.red与 Rectangle().fill(.red)的效果


图片

默认的情况下,Image 视图会报告一个固定值:也就是它所持有的图⽚的尺⼨。

Image 上调⽤ .resizable(),这个视图就会完全灵活可变: Image 将会接受任意的建议尺⼨,并将它报告回去,同时将图像压缩或者拉伸到这个尺⼨。

在实践中,实际上任何⼀个 resizable 的图⽚都会

.aspectRatio(contentMode:).scaleToFit() 修饰器⼀同使⽤,以避免图⽚产⽣变形。

分割线

在⽔平堆栈 (⽐如 HStack) 外使⽤ Divider 时,它接受任意的建议宽度,并将分隔线⾃身的默认⾼度进⾏汇报。

在⽔平堆栈内部使⽤时,分隔线会接受建议的⾼度,并报告分隔线的宽度。如果建议值是 nil,那么它将根据所处环境,在可变轴上使⽤默认尺⼨ 10

VStack {
    Spacer()
    VStack {
        // 在⽔平堆栈外使⽤ Divider
        HStack {
            Text("Divider outside of HStack")
        }
        Divider()
    }
    .padding()
    .border(.green.gradient)
    
    Spacer()
    
    // 在⽔平堆栈内使⽤ Divider
    VStack {
        HStack {
            Text("Divider inside of HStack")
            
            Divider()
        }
    }
    .padding()
    .border(.green.gradient)
    
    Spacer()
    
    // 在⽔平堆栈外使⽤ Divider,且建议值为 nil
    VStack {
        Text("Divider outside of HStack with nil proposal")
        Divider()
            .fixedSize()
            .background(.red.gradient)
    }
    .padding()
    .border(.green.gradient)
    
    Spacer()
    
    // 在⽔平堆栈内使⽤ Divider,且建议值为 nil
    VStack {
        HStack {
            Text("Divider inside of HStack with nil proposal")
            
            Divider()
                .fixedSize()
                .background(.red.gradient)
        }
    }
    .padding()
    .border(.green.gradient)
}

分割线
分割线


Spacer

在⽔平堆栈和垂直堆栈之外,Spacer 接受从最⼩⻓度⽆限⼤的所有建议尺⼨。

在垂直堆栈中,Spacer 接受从其最⼩⻓度到⽆限⼤的任何⾼度,但报告的宽度为零。在⽔平堆栈中,它的⾏为相同 (只是轴互换)。除⾮使⽤ Spacer 初始化⽅法中的 minLength 参数指定⼀个⻓度,否则 Spacer 的最⼩⻓度会是默认填充 (padding) 的⻓度

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容