尝试使用几何 reader 和导航栏获得完整的视图宽度 - SwiftUI
Trying to get full width of view using geometry reader and navigation bar - SwiftUI
我有 2 个 HStack
使用几何 reader 将它们平均分成 2 个部分嵌入到 VStack
中,我正在尝试创建类似于首先是下图(iPad 上的横向模式)。
但是,我正在努力让 HStack
像中间的网格会议一样排队。我还有一个 NavigationView
侧边栏,它可以与 HStack
一起显示,因此理想情况下,2 张图像会改变宽度但保持高度不变,而不会挤压或拉伸图像。我尝试使用 clipped()
.
来做到这一点
下面的第二张图片是我 运行 我的代码时得到的。我已将此示例中的图像替换为 SFSymbol 以使其更易于调试。
这是在我的 ContentView
中调用的 NavigationView
侧边栏:
struct SideBar: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: DetailView()) {
Label("Products", systemImage: "printer")
}
Label("Comparison", systemImage: "simcard.2")
Label("Search", systemImage: "magnifyingglass")
}
.listStyle(SidebarListStyle())
.navigationTitle("Navigation")
DetailView()
}
}
}
这是包含内容的主视图:
struct DetailView: View {
let title = "This is a title"
let paragraph = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
let image = "dot.squareshape.fill"
let intPadding: CGFloat = 10
let extPadding: CGFloat = 40
var body: some View {
VStack(spacing: 0){
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0){
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
.clipped()
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
.frame(width: geometry.size.width / 2)
}
}
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0){
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.frame(width: geometry.size.width / 2)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
.offset(x: -intPadding)
.clipped()
}
}
}
}
}
编辑:
黑色方块代表图像要去的地方,我没有放黑色方块。所以上面提到的挤压和拉伸的想法应该看起来像下图,所以它实际上并没有拉伸或挤压图像只是边界框:
这是一个可能的方法演示 - 使用 Color.clear
,因为它会平等地填充所有可用的内容,并在叠加层中添加内容。
准备 Xcode 12.1 / iOS 14.1
var body: some View {
VStack(spacing: 0) {
Color.clear.overlay(
HStack(spacing: 0) {
Color.clear.overlay(
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
)
Color.clear.overlay(
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
, alignment: .top)
},
alignment: .top)
Color.clear.overlay(
HStack(spacing: 0) {
Color.clear.overlay(
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
, alignment: .top)
Color.clear.overlay(
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.clipped()
, alignment: .top)
}
)
}
}
更新:全屏变体
VStack(spacing: 0) {
Color.clear.overlay(
HStack(spacing: 0) {
Color.black
// .overlay(
// Image(systemName:image)
// .resizable()
// .aspectRatio(contentMode: .fit)
// )
Color.clear.overlay(
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
, alignment: .top)
},
alignment: .top)
Color.clear.overlay(
HStack(spacing: 0) {
Color.clear.overlay(
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
, alignment: .top)
Color.black
// .overlay(
// Image(systemName:image)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .clipped()
// , alignment: .top)
}
)
}
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.all)
}
此布局的最大挑战是高度。在每个 HStack 行上,您都有可变的多行文本(可以根据可访问性、字体等更改大小)。如果文本比预期的长或用户增加了它,布局将无法保持。
要导出行高,您可以使用宽高比来设置图像框架和可变文本块的高度。这将锁定两个 img/text 的行高,因此无论屏幕宽度或文本长度如何,图像角始终接触。较长的文本最终会变成椭圆形,或者您可以使用 ScrollView 并将其高度设置为与图像派生的 aspectHeight 相同。
下面的代码清除了导致水平扩展问题的所有 padding/offsets 并使用图像纵横比 (16x9)。假设图像几乎是标准尺寸,或者使用您喜欢的任何尺寸(4x6 等)。请注意,图像未使用“适合”正确“缩放”,请使用 aspectRatio.fill 将图像从中心均匀拉伸。
如果您不想预先定义图像宽高比,并且需要图像具有像素完美的宽高比,Swift可以预加载图像文件以获取宽高比:
struct DetailView: View {
let title = "This is a title"
let paragraph = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
let image = "dot.squareshape.fill"
let intPadding: CGFloat = 20
let extPadding: CGFloat = 20 // reduced extPadding, it seemed high
var body: some View {
GeometryReader { geometry in
let halfWidth:CGFloat = (geometry.size.width * 0.5) - extPadding
let aspectHeight:CGFloat = halfWidth * (9/16) // images are 16x9 aspect ratio
// hstack centers layout after extPadding is subracted from desired width
HStack(alignment: .top) {
Spacer() // left margin
VStack(alignment:.center, spacing:0) {
// row 1
HStack(spacing:0) {
Image(systemName:image)
.resizable()
.aspectRatio(contentMode:.fill) // zoom image keeping correct aspect
.frame(width:halfWidth, height:aspectHeight, alignment: .center) // center image to frame
.clipped() // clipped crops off the img sides outside the frame
VStack(alignment:.leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
}
.padding(intPadding)
.frame(width:halfWidth, height:aspectHeight, alignment: .top) // height stops vertical spread of images due to text length
}
// row 2
HStack(spacing:0) {
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(intPadding)
.frame(width:halfWidth, height:aspectHeight, alignment: .top)
Image(systemName: image)
.resizable()
.aspectRatio(contentMode:.fill) // zoom image keeping correct aspect
.frame(width:halfWidth, height:aspectHeight, alignment: .center) // center image to frame
.clipped() // clipped crops off the img sides outside the frame
}
}
Spacer() // right margin
}
} // geo
} // body
}
注意:LazyVStack 具有网格列功能,但需要 iOS14+,所以我坚持使用 VStack/HStack。
我有 2 个 HStack
使用几何 reader 将它们平均分成 2 个部分嵌入到 VStack
中,我正在尝试创建类似于首先是下图(iPad 上的横向模式)。
但是,我正在努力让 HStack
像中间的网格会议一样排队。我还有一个 NavigationView
侧边栏,它可以与 HStack
一起显示,因此理想情况下,2 张图像会改变宽度但保持高度不变,而不会挤压或拉伸图像。我尝试使用 clipped()
.
下面的第二张图片是我 运行 我的代码时得到的。我已将此示例中的图像替换为 SFSymbol 以使其更易于调试。
这是在我的 ContentView
中调用的 NavigationView
侧边栏:
struct SideBar: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: DetailView()) {
Label("Products", systemImage: "printer")
}
Label("Comparison", systemImage: "simcard.2")
Label("Search", systemImage: "magnifyingglass")
}
.listStyle(SidebarListStyle())
.navigationTitle("Navigation")
DetailView()
}
}
}
这是包含内容的主视图:
struct DetailView: View {
let title = "This is a title"
let paragraph = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
let image = "dot.squareshape.fill"
let intPadding: CGFloat = 10
let extPadding: CGFloat = 40
var body: some View {
VStack(spacing: 0){
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0){
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
.clipped()
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
.frame(width: geometry.size.width / 2)
}
}
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0){
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.frame(width: geometry.size.width / 2)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
.offset(x: -intPadding)
.clipped()
}
}
}
}
}
编辑:
黑色方块代表图像要去的地方,我没有放黑色方块。所以上面提到的挤压和拉伸的想法应该看起来像下图,所以它实际上并没有拉伸或挤压图像只是边界框:
这是一个可能的方法演示 - 使用 Color.clear
,因为它会平等地填充所有可用的内容,并在叠加层中添加内容。
准备 Xcode 12.1 / iOS 14.1
var body: some View {
VStack(spacing: 0) {
Color.clear.overlay(
HStack(spacing: 0) {
Color.clear.overlay(
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
)
Color.clear.overlay(
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
, alignment: .top)
},
alignment: .top)
Color.clear.overlay(
HStack(spacing: 0) {
Color.clear.overlay(
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
, alignment: .top)
Color.clear.overlay(
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.clipped()
, alignment: .top)
}
)
}
}
更新:全屏变体
VStack(spacing: 0) {
Color.clear.overlay(
HStack(spacing: 0) {
Color.black
// .overlay(
// Image(systemName:image)
// .resizable()
// .aspectRatio(contentMode: .fit)
// )
Color.clear.overlay(
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
, alignment: .top)
},
alignment: .top)
Color.clear.overlay(
HStack(spacing: 0) {
Color.clear.overlay(
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
, alignment: .top)
Color.black
// .overlay(
// Image(systemName:image)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .clipped()
// , alignment: .top)
}
)
}
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.all)
}
此布局的最大挑战是高度。在每个 HStack 行上,您都有可变的多行文本(可以根据可访问性、字体等更改大小)。如果文本比预期的长或用户增加了它,布局将无法保持。
要导出行高,您可以使用宽高比来设置图像框架和可变文本块的高度。这将锁定两个 img/text 的行高,因此无论屏幕宽度或文本长度如何,图像角始终接触。较长的文本最终会变成椭圆形,或者您可以使用 ScrollView 并将其高度设置为与图像派生的 aspectHeight 相同。
下面的代码清除了导致水平扩展问题的所有 padding/offsets 并使用图像纵横比 (16x9)。假设图像几乎是标准尺寸,或者使用您喜欢的任何尺寸(4x6 等)。请注意,图像未使用“适合”正确“缩放”,请使用 aspectRatio.fill 将图像从中心均匀拉伸。
如果您不想预先定义图像宽高比,并且需要图像具有像素完美的宽高比,Swift可以预加载图像文件以获取宽高比:
struct DetailView: View {
let title = "This is a title"
let paragraph = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
let image = "dot.squareshape.fill"
let intPadding: CGFloat = 20
let extPadding: CGFloat = 20 // reduced extPadding, it seemed high
var body: some View {
GeometryReader { geometry in
let halfWidth:CGFloat = (geometry.size.width * 0.5) - extPadding
let aspectHeight:CGFloat = halfWidth * (9/16) // images are 16x9 aspect ratio
// hstack centers layout after extPadding is subracted from desired width
HStack(alignment: .top) {
Spacer() // left margin
VStack(alignment:.center, spacing:0) {
// row 1
HStack(spacing:0) {
Image(systemName:image)
.resizable()
.aspectRatio(contentMode:.fill) // zoom image keeping correct aspect
.frame(width:halfWidth, height:aspectHeight, alignment: .center) // center image to frame
.clipped() // clipped crops off the img sides outside the frame
VStack(alignment:.leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
}
.padding(intPadding)
.frame(width:halfWidth, height:aspectHeight, alignment: .top) // height stops vertical spread of images due to text length
}
// row 2
HStack(spacing:0) {
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(intPadding)
.frame(width:halfWidth, height:aspectHeight, alignment: .top)
Image(systemName: image)
.resizable()
.aspectRatio(contentMode:.fill) // zoom image keeping correct aspect
.frame(width:halfWidth, height:aspectHeight, alignment: .center) // center image to frame
.clipped() // clipped crops off the img sides outside the frame
}
}
Spacer() // right margin
}
} // geo
} // body
}
注意:LazyVStack 具有网格列功能,但需要 iOS14+,所以我坚持使用 VStack/HStack。