为啥 SwiftUI UIHostingController 有额外的间距?
Posted
技术标签:
【中文标题】为啥 SwiftUI UIHostingController 有额外的间距?【英文标题】:Why does SwiftUI UIHostingController have extra spacing?为什么 SwiftUI UIHostingController 有额外的间距? 【发布时间】:2021-12-30 02:29:37 【问题描述】:我正在尝试使用 UIHostingController 将 SwiftUI 视图添加到 UIKit 视图,它显示了额外的间距(此示例用于模拟生产应用程序上的问题)。这是屏幕截图。
布局概览:
View
UIStackView
UIImageView
UIView(red)
UIHostingController
UIView(blue)
问题: swift UI 视图 (UIHostingController) 显示在红色和蓝色视图之间,它在分隔线后显示额外的间距。间距根据 SwiftUI 视图的大小而变化。
如果我减少行数(Hello World 文本)或减少间距,它似乎工作正常。
这里是完整的源代码(https://www.sendspace.com/file/ux0xt7):
ViewController.swift(主视图)
import UIKit
class ViewController: UIViewController
@IBOutlet weak var mainStackView: UIStackView!
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view.
addView()
private func addView()
mainStackView.spacing = 0
mainStackView.alignment = .fill
let imageView = UIImageView()
imageView.image = UIImage(named: "mountain")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.heightAnchor.constraint(equalToConstant: 260).isActive = true
mainStackView.addArrangedSubview(imageView)
let redView = UIView()
redView.backgroundColor = .red
redView.heightAnchor.constraint(equalToConstant: 100).isActive = true
mainStackView.addArrangedSubview(redView)
let sampleVC = SampleViewController()
//let size = sampleVC.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
//sampleVC.view.heightAnchor.constraint(equalToConstant: size.height).isActive = true
mainStackView.addArrangedSubview(sampleVC.view)
let blueView = UIView()
blueView.backgroundColor = .blue
blueView.heightAnchor.constraint(equalToConstant: 100).isActive = true
mainStackView.addArrangedSubview(blueView)
SampleView.swift
import SwiftUI
struct SampleView: View
var body: some View
VStack(spacing: 0)
Text("Title")
VStack (alignment: .leading, spacing: 30)
Text("Hello World1")
Text("Hello World2")
Text("Hello World3")
Text("Hello World4")
Text("Hello World5")
Text("Hello World6")
Text("Hello World7")
Text("Hello World8")
Text("Hello World9")
Text("Hello World10")
Divider()
struct SampleView_Previews: PreviewProvider
static var previews: some View
Group
SampleView()
SampleViewController.swift
import UIKit
import SwiftUI
class SampleViewController: UIViewController
override func viewDidLoad()
super.viewDidLoad()
addView()
private func addView()
let hostingController = UIHostingController(rootView: SampleView())
hostingController.view.backgroundColor = .clear
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
hostingController.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
hostingController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
提前致谢!
【问题讨论】:
它看起来像一个错误 - 托管视图认为它在根目录中并将edgeAreaInsets
应用于内部 SwiftUI 内容。
【参考方案1】:
除了UIView(red)
,您可以简单地使用Color.red
与UIView(blue)
相同:
struct SampleView: View
var body: some View
VStack(spacing: .zero)
Color.red
Text("Title")
VStack (alignment: .leading, spacing: 30)
Text("Hello World1")
Text("Hello World2")
Text("Hello World3")
Text("Hello World4")
Text("Hello World5")
Text("Hello World6")
Text("Hello World7")
Text("Hello World8")
Text("Hello World9")
Text("Hello World10")
Divider()
Color.blue.opacity(0.1)
结果:
【讨论】:
这是一个简单的演示应用程序,它从真实应用程序中模拟了问题。如果您将 Color.red 或 blue 视图替换为其他视图(例如图像),您可以很容易地看到布局中断。 我相信您想要的任何东西都可以用红色和蓝色替换,没有任何问题!也许你使用错误的方式! @丹尼 唯一的 swift ui 视图是白色背景的中间视图。上面的红色和蓝色视图是我的示例应用程序中的 UIKit 视图。当 swift ui 视图与滚动视图中的 UIKit 视图混合时,就会发生这种情况。 如果您仅使用少量视图,则不会发生这种情况。 没问题,你的设计和计划是你对可能的东西说不,最好的方法是不使用它或以正确的方式使用它。【参考方案2】:让宿主控制器的视图再次更新其布局:
class SampleViewController: UIViewController
override func viewDidLoad()
super.viewDidLoad()
addView()
private func addView()
let hostingController = UIHostingController(rootView: SampleView())
hostingController.view.backgroundColor = .clear
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
hostingController.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
hostingController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
hostingController.view.layoutIfNeeded()
【讨论】:
它不起作用。 奇怪。你不能只在VStack
的底部添加一个填充而不是使用Divider
,或者最终尝试将其包装在AnyView
中,然后在主机控制器中使用它:let hostingController = UIHostingController(rootView: AnyView(SampleView()))
?
添加分隔线以显示错误造成的额外间距。
@Danny 你试过用AnyView
包装吗?
是的,它不起作用。【参考方案3】:
我遇到了类似的问题,就像 @Asperi mentions,由于 SwiftUI 视图应用了额外的安全区域插图。
很遗憾,简单地将edgesIgnoringSafeArea()
添加到 SwiftUI 视图中是行不通的。
相反,您可以使用以下 UIHostingController
扩展来解决此问题:
extension UIHostingController
convenience public init(rootView: Content, ignoreSafeArea: Bool)
self.init(rootView: rootView)
if ignoreSafeArea
disableSafeArea()
func disableSafeArea()
guard let viewClass = object_getClass(view) else return
let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea")
if let viewSubclass = NSClassFromString(viewSubclassName)
object_setClass(view, viewSubclass)
else
guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else return
guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else return
if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets))
let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = _ in
return .zero
class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))
objc_registerClassPair(viewSubclass)
object_setClass(view, viewSubclass)
并像这样使用它:
let hostingController = UIHostingController(rootView: SampleView(), ignoreSafeArea: true)
解决方案出处:https://defagos.github.io/swiftui_collection_part3/#fixing-cell-frames
【讨论】:
以上是关于为啥 SwiftUI UIHostingController 有额外的间距?的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI,为啥部分视图没有刷新? @State 变量未更新
SwiftUI:为啥这个 ScrollView 的内容错位了?