SwiftUI 讓視圖自適應高度的 6 種方法(四)

在這里插入圖片描述

概覽

在 SwiftUI 的世界里,我們無數次都夢想著視圖可以自動根據布局上下文“因勢而變”?。大多數情況下,SwiftUI 會將每個視圖尺寸處理的井井有條,不過在某些時候我們還是得親力親為。

在這里插入圖片描述

如上圖所示,無論頂部 TabView 容器里子視圖高度如何變化,TabView 本身的高度都能“隨遇而安”。如何用最簡單、最現代化、最有趣且最切中要害的方法讓容器尺寸與子視圖的高度“如影隨形”呢?

在本篇博文中,您將學到如下內容:

  • 概覽
  • 9. 最“相得益彰”的實現:自定義布局 Layout
    • 9.1 重裝上陣 Layout
    • 9.2 “奇怪的” TabView
    • 9.3 MaxHeightLayout 的實現
  • 總結

相信學完本課后,小伙伴們必能腦洞大開、格局打開,用“千姿百態”的方法讓問題的解決一發入魂、九轉功成!

那還等什么呢?Let‘s go!!!😉


9. 最“相得益彰”的實現:自定義布局 Layout

在一口氣介紹完上面 5 種“五花八門”的實現之后,我們完全可以“鳴金收兵”。但是為了面面俱到,我們最后還是決定用自定義布局 Layout 來為整個系列博文畫一個圓滿的句號。

9.1 重裝上陣 Layout

所謂自定義布局 Layout,其實就是創建一款遵守 Layout 協議的“容器”(嚴格說應該是視圖集合 Collection of views),然后“恣意”為內部的子視圖“排兵布陣”:

在這里插入圖片描述

為什么說用 Layout 這種方法更加“鞭辟入里”呢?因為這是處理多個同一層級子視圖布局最自然的方式。

大家回憶一下:我們是將所有喜愛的成語用 ForEach 挨個放在 TabView 容器里的,在父容器中對它們的布局“運籌帷幄”是理所當然的事。


關于自定義布局的進一步介紹,請小伙伴們移步如下鏈接觀賞精彩的文章:

  • SwiftUI 打造一款收縮自如的 HStack(四):Layout 自定義布局

9.2 “奇怪的” TabView

我們的目標是創建一個通用自定義布局 MaxHeightLayout,然后實時計算出所有子視圖中最高的 Height。由于 MaxHeightLayout 是作為一個“容器”放在 TabView 中的,我們必須顯式設置 TabView 的高度,而不能通過設置 MaxHeightLayout 的高度來間接影響 Tabview。

為什么會這樣呢?這是由于 TabView 自身的特殊性質造成的。

比如在下面的代碼中,我們在 TabView 里放置了一個高度為 200 的圓形:

TabView {Circle().foregroundStyle(.green.gradient).frame(height: 200)
}
.tabViewStyle(.page)

盡管我們將內部圓形的高度設置為 200,明確“暗示” TabView 把自己的高度也做出相應調整 ,但 TabView 還是會無動于衷:

在這里插入圖片描述

要想 TabView 能夠充分容納高度為 200 的圓形,我們必須將 TabView 的高度顯式設置為 200:

TabView {Circle().foregroundStyle(.green.gradient)
}
.tabViewStyle(.page)
.frame(height: 200)

在這里插入圖片描述

換句話說,TabView 不會站在子視圖的角度考慮問題,它會完全忽略子視圖尺寸的提議,“一意孤行”。

9.3 MaxHeightLayout 的實現

上面討論的結果迫使我們必須讓自定義布局 MaxHeightLayout 想辦法將計算產生的最大高度傳遞向外給 TabView 才行。

有很多種方法可以達到目的,這里我們采用最簡單的一種:綁定(Binding)。

struct MaxHeightLayout: Layout {var spacing: CGFloat?@Binding var maxHeight: CGFloatfunc sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {let proposalWidth = proposal.width!let idealViewSizes = subviews.map { $0.sizeThatFits(.init(width: proposalWidth / CGFloat(subviews.count), height: nil)) }let totalHeight = idealViewSizes.map {$0.height}.max() ?? 0.0// 防止反復賦值造成渲染循環if totalHeight > maxHeight {maxHeight = totalHeight}return CGSize(width: proposalWidth, height: totalHeight)}private func calcSpaces(subviews: Subviews) -> [CGFloat] {if let spacing {[CGFloat](repeating: spacing, count: subviews.count - 1)} else {subviews.indices.map { idx inguard idx < subviews.count - 1 else { return 0 }return subviews[idx].spacing.distance(to: subviews[idx+1].spacing, along: .horizontal)}}}func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {let spaces = calcSpaces(subviews: subviews)var point = CGPoint(x: bounds.minX, y: bounds.minY)let subviewWidth = bounds.width / CGFloat(subviews.count)for idx in subviews.indices {subviews[idx].place(at: point, proposal: .init(width: subviewWidth - spaces[idx], height: maxHeight))if idx < subviews.count - 1 {point.x += subviewWidth - spaces[idx]}}}    
}

在上面的代碼中,我們主要做了這么幾件事:

  • 讓 MaxHeightLayout “容器”中每個子視圖的寬都平分容器的寬度;
  • 用 calcSpaces 方法計算子視圖間的空隙,并確保 placeSubviews 方法在布局子視圖時應用它們;
  • 只在必要時更新 maxHeight 綁定的值(totalHeight > maxHeight 時),這是避免“遞歸渲染”的重要手段;

最后,只要將 TabView 中原來內層的 ForEach 循環以及相關邏輯放在 MaxHeightLayout 里就可以啦:

Section("喜愛的成語") {TabView {ForEach(likeIdioms.chunked(into: 2), id: \.self) { idiomChunk inVStack {MaxHeightLayout(maxHeight: $maxHeight) {                    ForEach(idiomChunk) { idiom inlikeIdiomCard(idiom)}if idiomChunk.count < 2 {Rectangle().foregroundStyle(.clear)}}Spacer()}}}.tabViewStyle(.page).frame(height: maxHeight).padding(.bottom, 8)
}

運行代碼可以發現結果和其它的實現毫無二致!

在這里插入圖片描述

借助自定義布局 Layout 的靈活性,我們可以非常輕松的改變 TabView 中成語顯示的數量,比如改為 3 列也不在話下:

在這里插入圖片描述

至此,我們圓滿完成了本系列博文中的所有任務。禿頭小伙伴們還不趕緊給自己一個大大的贊吧!愛你們哦!?


想要進一步系統地學習 Swift 開發的小伙伴們,可以來我的《Swift 語言開發精講》專欄逛一逛哦:

在這里插入圖片描述

  • 《Swift 語言開發精講》

總結

在本篇博文中,我們介紹了如何使用自定義布局 Layout 來實現 SwiftUI 視圖高度的“遙相呼應”,精彩的大結局小伙伴們不容錯過哦!

感謝觀賞,再會啦!😎

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/73087.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73087.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73087.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

小程序SSL證書過期怎么辦?

SSL證書就像小程序的“安全鎖”&#xff0c;一旦過期&#xff0c;用戶訪問時會被提示“不安全”&#xff0c;輕則流失客戶&#xff0c;重則數據泄露&#xff01;作為企業負責人&#xff0c;如何快速解決證書過期問題&#xff1f;又該如何避免再次踩坑&#xff1f;這篇指南給你答…

ClickHouse優化技巧實戰指南:從原理到案例解析

目錄 ?ClickHouse優化核心思想?表結構設計優化?查詢性能優化技巧?數據寫入優化方案?系統配置調優實戰?高可用與集群優化?真實案例解析?總結與建議 1. ClickHouse優化核心思想 ClickHouse作為OLAP領域的明星引擎&#xff0c;其優化需遵循列式存儲特性&#xff0c;把握…

DeepSeek 助力 Vue3 開發:打造絲滑的表格(Table)之添加列寬調整功能,示例Table14_02帶邊框和斑馬紋的固定表頭表格

前言&#xff1a;哈嘍&#xff0c;大家好&#xff0c;今天給大家分享一篇文章&#xff01;并提供具體代碼幫助大家深入理解&#xff0c;徹底掌握&#xff01;創作不易&#xff0c;如果能幫助到大家或者給大家一些靈感和啟發&#xff0c;歡迎收藏關注哦 &#x1f495; 目錄 Deep…

服務自動被kill掉的原因和查看

服務在運行一段時間后被自動kill掉可能是由多種原因引起的,包括系統資源限制、進程管理策略、應用程序錯誤等。以下是一些常見的原因以及定位問題的過程: 常見原因 系統資源限制: 內存不足:如果服務消耗了過多的內存,系統可能會kill掉該進程以釋放內存資源。CPU使用過高:…

基礎算法——順序表

一、詢問學號 題?來源&#xff1a;洛? 題?鏈接&#xff1a;P3156 【深基15.例1】詢問學號 - 洛谷 難度系數&#xff1a;★ 1. 題目描述 2. 算法原理 直接? vector 或者數組模擬即可。 3. 參考代碼 #include <iostream> #include <vector>using namespace st…

Ubuntu用戶安裝cpolar內網穿透

前言 Cpolar作為一款體積小巧卻功能強大的內網穿透軟件&#xff0c;不僅能夠在多種環境和應用場景中發揮巨大作用&#xff0c;還能適應多種操作系統&#xff0c;應用最為廣泛的Windows、Mac OS系統自不必多說&#xff0c;稍顯小眾的Linux、樹莓派、群輝等也在起支持之列&#…

C#實現高性能異步文件下載器(支持進度顯示/斷點續傳)

一、應用場景分析 異步文件下載器用處很大&#xff0c;當我們需要實現以下功能時可以用的上&#xff1a; 大文件下載&#xff08;如4K視頻/安裝包&#xff09; 避免UI線程阻塞&#xff0c;保證界面流暢響應多任務并行下載 支持同時下載多個文件&#xff0c;提升帶寬利用率后臺…

Oracle比較好的幾本書籍

1.《Oracle專家高級編程》 2.《Oracle高效設計》 3.《Oracle9i&10g&11g編程藝術深入數據庫體系結構》 4.《讓Oracle跑的更快》(1/2) ....... n.《Oracle官方文檔的閱讀》下面包括這幾個部分&#xff0c;可以跟進研讀一下&#xff1a; &#xff08;1&#xff09;《…

js和java中方法重載(js本身是不支持方法重載,方便對比學習)

js如果需要實現方法重載 示例 1&#xff1a;根據參數數量實現重載 function overloadExample() {if (arguments.length 1) {console.log(一個參數:, arguments[0]);} else if (arguments.length 2) {console.log(兩個參數:, arguments[0], arguments[1]);} else {console.l…

Android : Camera之CHI API

來自&#xff1a; https://www.cnblogs.com/szsky/articles/10861918.html 一、CAM CHI API功能介紹&#xff1a; CHI API建立在Google HAL3的靈活性基礎之上&#xff0c;目的是將Camera2/HAL3接口分離出來用于使用相機功能&#xff0c;它是一個靈活的圖像處理驅動程序&#…

Netty基礎—2.網絡編程基礎四

大綱 1.網絡編程簡介 2.BIO網絡編程 3.AIO網絡編程 4.NIO網絡編程之Buffer 5.NIO網絡編程之實戰 6.NIO網絡編程之Reactor模式 5.NIO網絡編程之Buffer (1)Buffer的作用 Buffer的作用是方便讀寫通道(Channel)中的數據。首先數據是從通道(Channel)讀入緩沖區&#xff0c;從…

Git前言(版本控制)

1.Git 目前世界上最先進的分布式版本控制系統。 git官網&#xff1a;https://git-scm.com/ 2.版本控制 2.1什么是版本控制 版本控制(Revision control)是一種在開發的過程中用于管理我們對文件、目錄或工程等內容修改歷史&#xff0c;方便查看更改歷史記錄備份以便恢復以前…

調試正常 ≠ 運行正常:Keil5中MicroLIB的“量子態BUG”破解實錄

調試正常 ≠ 運行正常&#xff1a;Keil5中MicroLIB的“量子態BUG”破解實錄——從勾選一個選項到理解半主機模式&#xff0c;嵌入式開發的認知升級 &#x1f4cc; 現象描述&#xff1a;調試與燒錄的詭異差異 在線調試時 程序正常運行 - 獨立運行時 設備無響應 ! 編譯過程 0 Err…

算法每日一練 (9)

&#x1f4a2;歡迎來到張胤塵的技術站 &#x1f4a5;技術如江河&#xff0c;匯聚眾志成。代碼似星辰&#xff0c;照亮行征程。開源精神長&#xff0c;傳承永不忘。攜手共前行&#xff0c;未來更輝煌&#x1f4a5; 文章目錄 算法每日一練 (9)最小路徑和題目描述解題思路解題代碼…

【高項】信息系統項目管理師(四)項目整合管理【4分】

一、管理基礎 項目整合管理的責任不能被授權或轉移&#xff0c;項目經理必須對整個項目承擔最終責任。 執行項目整合時項目經理承擔雙重角色&#xff1a; 1、組織層面上&#xff0c;項目經理扮演重要角色&#xff0c;與項目發起人攜手合作&#xff0c;了解戰略目標并確保項目目…

ECEF與ENU坐標系定義及C語言實現

一、ECEF與ENU坐標系定義 ECEF坐標系&#xff08;地心地固坐標系&#xff09; 原點&#xff1a;地球質心X軸&#xff1a;指向本初子午線與赤道交點Y軸&#xff1a;在赤道平面內與X軸垂直Z軸&#xff1a;指向北極數學表示&#xff1a; P e c e f ( x , y , z ) P_{ecef} (x,…

sql語句分頁的關鍵字是?

在 SQL 中&#xff0c;分頁通常是通過限制查詢結果的數量并指定從哪一行開始獲取數據來實現的。不同的數據庫系統使用不同的分頁關鍵字。 以下是常見數據庫系統的分頁關鍵字&#xff1a; MySQL / PostgreSQL / SQLite 使用 LIMIT 和 OFFSET 來進行分頁&#xff1a; LIMIT 限…

大模型中的剪枝、蒸餾是什么意思?

環境&#xff1a; 剪枝 蒸餾 問題描述&#xff1a; 大模型中的剪枝、蒸餾是什么意思&#xff1f; 解決方案&#xff1a; 大模型的剪枝&#xff08;Pruning&#xff09;和蒸餾&#xff08;Distillation&#xff09;是兩種常見的模型優化技術&#xff0c;用于減少模型的大小…

初次體驗Tauri和Sycamore(3)通道實現

? 原創作者&#xff1a;莊曉立&#xff08;LIIGO&#xff09; 原創時間&#xff1a;2025年03月10日&#xff08;發布時間&#xff09; 原創鏈接&#xff1a;https://blog.csdn.net/liigo/article/details/146159327 版權所有&#xff0c;轉載請注明出處。 20250310 LIIGO備注&…

代碼隨想錄|二叉樹|07二叉樹周末總結

對前面01~06二叉樹內容進行小結&#xff0c;直接看下面的總結文檔&#xff1a; 本周小結&#xff01;&#xff08;二叉樹&#xff09; | 代碼隨想錄