SwiftUI之深入解析Frame Behaviors

一、Frame 简介

  • 当开始使用 SwiftUI 时,可能接触到的第一个修饰符是 frame(width:height:alignment),定义 frame 是 SwiftUI 最具挑战性的任务之一,当我们使用修饰符(如 .frame().)时,会发生很多事情。
  • SwiftUI 中的修饰符实际上并不修改视图,大多数情况下,当在视图上应用修饰符时,会创建一个新视图,该视图围绕着被“modified”的视图,需将包装器视图视为 frame。虽然视图没有框架(至少在 UIKit/AppKit 意义上没有),但它们确实有边界,这些边界不能直接操作,它们是视图及其父视图的紧急属性。
  • 其实 SwiftU 中的 modifies 并不是改变 View 上的某个属性而是用一个带有相关属性的新 View 来包装原有的 View,当然 .frame 也不例外。frame 可能对其子元素的大小有影响,也可能没有。例如,有些扩展到占据整个框架,有些则不会。接下来,我们着重分析 .frame(), .fixedSize() 和 .layoutPriority().。
  • 有时间,可以将 views 有趣地分为:generous、well-behaved、selfish 和 badly-behaved,使用这些术语可能会导致大家认为其中一些类型可能是不受欢迎的。这不是我的意图,只是想提供一个有趣的心理画面,以方便记住它们。但为了清楚起见,我将省略这些名称,只描述其行为。
  • 可以发现通过它们对所提供空间的反应来对观点进行分组:
    • Views 将只占用尽可能多的空间,以适应自己的内容,如 Stack containers。
    • Views 只会获取它们所需要的,如果提供给它们的空间不足以画出所有的内容,它们会尽最大努力尊重提供的空间。Text views 是一个混合的例子:如果没有足够的水平空间,它们将截断或换行文本。然而,无论框架有多小,至少会显示一行文本。
    • Views 将增长以填满所有提供的空间(但不能再多一个像素),形状通常是一个很好的例子,比如 Rectangle()。
    • Views 甚至可能决定在父方提供的区域之外绘制的视图,一些自定义视图可以使用这种方法。
  • 请注意,Views 在每个轴上的行为可能不同。例如,VStack 中的 Spacer 可能会在垂直方向上占用所有空间,但不会在水平方向上占用任何空间。话虽如此,在某些情况下,一个轴的行为会受到另一个轴的影响。Text views 视图就是这样一个例子,因为它的高度可能取决于建议的宽度。
  • 通过使用 .frame() 修饰符,没有直接改变行为,而是改变它们工作的上下文,从而导致不同的行为。

二、基本的 Frame

  • 使用 frame() 的最基本方式:
func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center)
  • 在我之前的博客 SwiftUI之深入解析Alignment Guides的超实用实战教程 中分析了对齐参数,可以帮你了解关于对齐参数如何影响布局的信息。当使用上面的这个方法时,看起来是在强制视图的宽度和高度,这通常是所达到的视觉效果。然而,事实并非如此,我们真正在做的是改变报价的大小,视图将如何处理它,将取决于视图本身,大多数视图将调整到新的提供的大小,这可能导致错误地假设强制视图的大小。我们没有。
  • 如下所示,使用 .frame() 来改变提供给子进程的内容,如果它小于 120,子视图仍然不会接受它,然而蓝色边框确实显示了框架(即在本文开头讨论的包装器),它确实被强制设置为新的大小:

请添加图片描述

struct ExampleView: View {
    @State private var width: CGFloat = 50
    
    var body: some View {
        VStack {
            SubView()
                .frame(width: self.width, height: 120)
                .border(Color.blue, width: 2)
            
            Text("Offered Width \(Int(width))")
            Slider(value: $width, in: 0...200, step: 1)
        }
    }
}


struct SubView: View {
    var body: some View {
        GeometryReader { proxy in
            Rectangle()
                .fill(Color.yellow.opacity(0.7))
                .frame(width: max(proxy.size.width, 120), height: max(proxy.size.height, 120))
        }
    }
}
  • 请注意,这些参数可以不指定或设置为 nil,在这种情况下,子进程将接收 original offer (axis),就好像根本没有调用 frame() 一样。大多数情况下,使用这个版本的 frame() 修饰符。

三、Frame 行为改变

  • 另一个 frame() 修饰符是这样的:
func frame(minWidth: CGFloat? = nil, idealWidth: CGFloat? = nil, maxWidth: CGFloat? = nil, minHeight: CGFloat? = nil, idealHeight: CGFloat? = nil, maxHeight: CGFloat? = nil, alignment: Alignment = .center)
  • 来分析一下,有三组参数,分别是影响宽度、高度的,还有对齐参数。在另外两组中,一组处理宽度,另一组处理高度,还要注意,参数可以不指定,在这种情况下,我们将观察到不同的行为。
  • 每组有三个参数:minimum、ideal 和 maximum,必须按升序给出这些值,或者不指定,即最小参数不能大于理想参数,理想参数不能大于最大参数。在过去,不遵守这些指导方针会导致崩溃,现在 SwiftUI 将产生一些“未指定和合理”的结果,但仍然会记录一条消息(“指定的矛盾框架约束”),可以让我们知道做错了什么。这个方法是做什么的呢?它将视图定位在具有指定宽度和高度约束的不可见框架中,详情如下:
    • 建议的框架子尺寸将是建议的框架尺寸,受任何指定的约束,建议中任何未指定的尺寸由相应的理想尺寸替换(如果指定)。
    • 框架在任何维度上的大小都遵循这个逻辑:

在这里插入图片描述

四、Views 固定大小

  • .fixedSize() 方法有两个版本:
func fixedSize() -> some View
func fixedSize(horizontal: Bool, vertical: Bool) -> some View
  • 第一个选项只是一种快速的呼叫方式:
fixedSize(horizontal: true, vertical: true)
  • 当为给定的 axis 设置固定的尺寸时,视图将被提供其理想尺寸(即在 .frame() 修饰符的 idealWidth/idealHeight 参数中指定的尺寸)。注意,视图总是有一个理想的维度,但是可以调用 .frame(idealWidth:idealHeight) 来创建一个类似的视图,但是具有不同的理想维度。一个具有有趣的理想维度的视图是 Text 视图,文本视图使用文本字符串和字体,以便得出理想的大小。
  • 如下所示,截断文本,由于父元素不够大,绿色边框表示框架的边界,蓝色边框表示文本的边界:
struct ExampleView: View {
    var body: some View {
        Text("hello there, this is a long line that won't fit parent's size.")
            .border(Color.blue)
            .frame(width: 200, height: 100)
            .border(Color.green)
            .font(.title)
    }
}
  • 然而,如果让文本视图使用尽可能多的宽度需要,这是结果:
struct ExampleView: View {
    var body: some View {
        Text("hello there, this is a long line that won't fit parent's size.")
            .fixedSize(horizontal: true, vertical: false)
            .border(Color.blue)
            .frame(width: 200, height: 100)
            .border(Color.green)
            .font(.title)
    }
}
  • 如下所示的动画显示了使用 fixedSize 和不指定 fixedSize 之间的区别,当将大小设置为固定时,文本视图可以展开并显示其所有的荣耀:

在这里插入图片描述

五、示例

  • 在下面的示例中,我们将复制 Text 行为,但是使用自定义视图:

请添加图片描述

  • 这哭将其命名为 LittleSquares,它将接收一个参数,其中包含要在单行中绘制的正方形数量,所有方块的大小为 20×20,颜色为绿色。但是,当父视图提供的宽度限制视图时,希望只绘制尽可能多的正方形,并将颜色更改为红色,以指示缺少正方形,这相当于截断文本视图并在末尾显示省略号(…)字符。此外,我们希望 litlesquares 视图的增长不要超出实际需要的范围。最后,视图必须设置它自己的理想宽度,所以当父视图使用 .fixedsize() 时,SwiftUI 知道它需要增长多少。
  • 为了解决所有这些问题,只需要确保使用 GeometryReader 来获取已提供的大小。通过代理宽度,可以知道可以画多少个正方形,如果少于总数,将它们涂成红色,否则用绿色。如果以前使用过 GeometryReader,你就知道它喜欢使用所有可用的空间,更合适的说法是:它在两个方向上都可以无限调整大小,这就是需要限制它的原因。注意,理想宽度和最大宽度是相等的,不希望视图超出其理想大小。
struct LittleSquares: View {
    let total: Int
    
    var body: some View {
        GeometryReader { proxy in

            // draw our view here

        }.frame(idealWidth: ???, maxWidth: ???)
    }
}
  • 完整的代码如下:
struct ExampleView: View {
    @State private var width: CGFloat = 150
    @State private var fixedSize: Bool = true
    
    var body: some View {
        GeometryReader { proxy in
            
            VStack {
                Spacer()
                
                VStack {
                    LittleSquares(total: 7)
                        .border(Color.green)
                        .fixedSize(horizontal: self.fixedSize, vertical: false)
                }
                .frame(width: self.width)
                .border(Color.primary)
                .background(MyGradient())
                
                Spacer()
                
                Form {
                    Slider(value: self.$width, in: 0...proxy.size.width)
                    Toggle(isOn: self.$fixedSize) { Text("Fixed Width") }
                }
            }
        }.padding(.top, 140)
    }
}

struct LittleSquares: View {
    let sqSize: CGFloat = 20
    let total: Int
    
    var body: some View {
        GeometryReader { proxy in
            HStack(spacing: 5) {
                ForEach(0..<self.maxSquares(proxy), id: \.self) { _ in
                    RoundedRectangle(cornerRadius: 5).frame(width: self.sqSize, height: self.sqSize)
                        .foregroundColor(self.allFit(proxy) ? .green : .red)
                }
            }
        }.frame(idealWidth: (5 + self.sqSize) * CGFloat(self.total), maxWidth: (5 + self.sqSize) * CGFloat(self.total))
    }

    func maxSquares(_ proxy: GeometryProxy) -> Int {
        return min(Int(proxy.size.width / (sqSize + 5)), total)
    }
    
    func allFit(_ proxy: GeometryProxy) -> Bool {
        return maxSquares(proxy) == total
    }
}

struct MyGradient: View {
    var body: some View {
        LinearGradient(gradient: Gradient(colors: [Color.red.opacity(0.1), Color.green.opacity(0.1)]), startPoint: UnitPoint(x: 0, y: 0), endPoint: UnitPoint(x: 1, y: 1))
    }
}

六、Layout 优先

  • 可能您以前可能使用过 .layourpriority() 方法,大多数时候,当事情不顺利时,会把它作为快速解决办法。暂停一下,分析一下它是做什么的以及它是如何做的。当多个 multiple siblings 竞争空间时,父节点将可用空间除以兄弟姐妹的数量并将其提供给第一个孩子,然后它将扣除它所占用的空间,并继续进行下一个孩子。
  • 这个简单的方法可以通过使用 .layoutpriority() 方法改变兄弟节点的优先级来改变,它只有一个参数,用来决定父节点如何对空间进行优先排序。所有视图的默认布局优先级为零,为了计算子视图将提供多少空间,父视图将遵循与前面类似的逻辑,但根据优先级对视图进行分组,并首先将空间分配给优先级较高的视图。

七、总结

  • frame 方法是在 SwiftUI 中使用的第一个修饰符之一,但正因为如此,我们通常不能完全理解它。一旦获得了一些经验,就有必要重新审视它并理解它所提供的每一个参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/312402.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

抵御爬虫的前线护盾:深度解读验证码技术的演变历程

一.前言 在当今信息技术迅速发展的背景下&#xff0c;网站和在线服务面临着日益增长的自动化访问威胁&#xff0c;这些大多来自于各类爬虫程序。这种大量的自动化访问不仅对网站的正常运行构成压力&#xff0c;还可能导致敏感数据的泄露&#xff0c;甚至被用于不正当竞争和恶意…

小议CompletableFuture 链式执行和响应式编程

相关文章&#xff1a; 用最简单的方式理解同步和异步、阻塞与非阻塞CompletableFuture 实战 背景 昨晚和一个朋友讨论了他在开发过程中遇到的一个场景设计问题。这个场景可以简化为&#xff1a;服务接收到一个需要处理的任务请求&#xff0c;然后立即返回。这个任务需要经过…

【Oracle】数据库查询与SQL语句

Oracle查询 一、单表查询 1、简单条件查询 1&#xff09;精确查询 SELECT* FROMT_OWNERS WHEREwatermeter 304082&#xff09;模糊查询 SELECT* FROMt_owners WHEREname LIKE %刘%3&#xff09;and运算符 SELECT* FROMt_owners WHEREname LIKE %刘% AND housenumb…

梦想贩卖机升级版知识付费源码,包含前后端源码,非线传,修复最新登录接口问题

梦想贩卖机升级版&#xff0c;变现宝吸收了资源变现类产品的许多优势&#xff0c;并剔除了那些无关紧要的元素&#xff0c;使得本产品在运营和变现能力方面实现了质的飞跃。多领域素材资源知识变现营销裂变独立版本。 支持&#xff1a;视频、音频、图文、文档、会员、社群、用…

【android】rk3588-android-bt

文章目录 蓝牙框架HCI接口蓝牙VENDORLIBvendorlib是什么 代码层面解读vendorlib1、 vendorlib实现&#xff0c;协议栈调用2、协议栈实现&#xff0c;vendorlib调用&#xff08;回调函数&#xff09;2.1、 init函数2.2、BT_VND_OP_POWER_CTRL对应处理2.3、BT_VND_OP_USERIAL_OPE…

基于 NFS 的文件共享实现

NFS&#xff08;Network File System&#xff09;即网络文件系统&#xff0c;它允许网络中的计算机之间通过 TCP/IP 网络共享文件资源&#xff0c;服务端通过 NFS 共享文件目录&#xff0c;客户端将该文件目录挂载在本地文件系统中&#xff0c;就可以像操作本地文件一样读写服务…

【AI视野·今日Robot 机器人论文速览 第七十二期】Mon, 8 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Mon, 8 Jan 2024 Totally 13 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Deep Reinforcement Learning for Local Path Following of an Autonomous Formula SAE Vehicle Authors Harvey Merton, Thoma…

wav2lip中文语音驱动人脸训练

1 Wav2Lip介绍 1.1 Wav2Lip概述 2020年&#xff0c;来自印度海德拉巴大学和英国巴斯大学的团队&#xff0c;在ACM MM2020发表了的一篇论文《A Lip Sync Expert Is All You Need for Speech to Lip Generation In The Wild 》&#xff0c;在文章中&#xff0c;他们提出一个叫做…

ChatGPT可以帮你做什么?

学习 利用ChatGPT学习有很多&#xff0c;比如&#xff1a;语言学习、编程学习、论文学习拆解、推荐学习资源等&#xff0c;使用方法大同小异&#xff0c;这里以语言学习为例。 在开始前先给GPT充分的信息&#xff1a;&#xff08;举例&#xff09; 【角色】充当一名有丰富经验…

vue3、vue2文件导入事件

一、vue3写法 1、html部分 <el-buttontype"info"plainicon"Upload"click"handleImport"v-hasPermi"[system:user:import]">导入</el-button><!-- 导入对话框 --><el-dialog :title"upload.title" v-…

性能分析与调优: Linux 磁盘I/O 观测工具

目录 一、实验 1.环境 2.iostat 3.sar 4.pidstat 5.perf 6. biolatency 7. biosnoop 8.iotop、biotop 9.blktrace 10.bpftrace 11.smartctl 二、问题 1.如何查看PSI数据 2.iotop如何安装 3.smartctl如何使用 一、实验 1.环境 &#xff08;1&#xff09;主机 …

【漏洞复现】先锋WEB燃气收费系统文件上传漏洞 1day

漏洞描述 /AjaxService/Upload.aspx 存在任意文件上传漏洞 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作…

ubuntu20固定串口名称

查看串口的详细信息 udevadm info --name/dev/ttyUSB0结果&#xff1a; P: /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.2/1-1.2:1.0/ttyUSB0/tty/ttyUSB0 N: ttyUSB0 L: 0 S: serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UAR…

Arrow:在项目中进行时间处理的强大工具

目录 一、Arrow简介 二、安装与配置 三、基础功能与使用 1. 日期和时间格式转换 2. 时区处理 3. 时间序列分析 四、进阶应用与案例分析 五、性能与优化 六、最佳实践与经验分享 七、总结与展望 在处理日期和时间时&#xff0c;我们经常需要一个精确、可靠的库来帮助我…

FinClip SaaS 平台——小程序转APP操作指南及其实现

目录 前言 优势 操作指南 IDE生成APP 打开导出目录查看APP 使用AS打开导出的项目 Application初始化sdk MainActivity启动小程序并finish掉当前页面 &#xff0c;可查看前面文章进行操作&#xff0c;本文介绍FinClip SaaS 平台推出的新功能 生成APP 前言 通过这个「生…

pytorch11:模型加载与保存、finetune迁移训练

目录 一、模型加载与保存1.1 序列化与反序列化概念1.2 pytorch中的序列化与反序列化1.3 模型保存的两种方法1.4 模型加载两种方法 二、断点训练2.1 断点保存代码2.2 断点恢复代码 三、finetune3.1 迁移学习3.2 模型的迁移学习3.2 模型微调步骤3.2.1 模型微调步骤3.2.2 模型微调…

计算机毕业设计 基于SpringBoot的物资综合管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

mysql原理--redo日志2

1.redo日志文件 1.1.redo日志刷盘时机 我们前边说 mtr 运行过程中产生的一组 redo 日志在 mtr 结束时会被复制到 log buffer 中&#xff0c;可是这些日志总在内存里呆着也不是个办法&#xff0c;在一些情况下它们会被刷新到磁盘里&#xff0c;比如&#xff1a; (1). log buffer…

ROS2——常见的指令

在使用source ∼/bookros_ws/install/setup.bash后&#xff0c;可以让ROS2找到这个工作空间&#xff0c;进而可以调用相关的命令 概述 ros2 <command> <verb> [<params>|<option>]*这是ROS2与系统交互的方式 在终端输入ros2&#xff0c;即可查看相关…

YOLOv5改进 | 检测头篇 | ASFFHead自适应空间特征融合检测头(全网首发)

一、本文介绍 本文给大家带来的改进机制是利用ASFF改进YOLOv5的检测头形成新的检测头Detect_ASFF,其主要创新是引入了一种自适应的空间特征融合方式,有效地过滤掉冲突信息,从而增强了尺度不变性。经过我的实验验证,修改后的检测头在所有的检测目标上均有大幅度的涨点效果,…