我正在尝试使用 UIHostingController 将 SwiftUI 视图添加到 UIKit 视图,它显示了额外的间距(此示例是为了模拟生产应用程序上的问题而制作的)。这是截图。
布局概览:
View
UIStackView
UIImageView
UIView(red)
UIHostingController
UIView(blue)
问题: 快速 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)
])
}
}
提前致谢!
我也遇到了类似的问题,就像 @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
虽然亚历山大的解决方案:https://stackoverflow.com/a/70339424/3390353对我有用。在运行时调整/更改内容总是让我在生产环境中感到有点紧张。
因此,我采用了 UIHostingController 子类的方法,当底部安全区域插入更改时,我可以使用 extraSafeAreaInsets 来“添加”当前底部 SafeArea Inset 的负值。通过检查仅在
safeAreaInsets.bottom > 0
时才执行此操作。
class OverrideSafeAreaBottomInsetHostingController<ContentView: SwiftUI.View>: UIHostingController<ContentView> {
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
if view.safeAreaInsets.bottom > 0 {
additionalSafeAreaInsets = .init(top: 0, left: 0, bottom: -view.safeAreaInsets.bottom, right: 0)
}
}
}
Swift 5.5 解决方案
尝试
UIHostingController(rootView: SampleView().edgesIgnoringSafeArea(.all))
让托管控制器的视图再次更新其布局:
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()
}
}
也许你可以这样尝试。
import SwiftUI
// Ignore safearea for UIHostingController when wrapped in UICollectionViewCell
class SafeAreaIgnoredHostingController<Content: View>: UIHostingController<Content> {
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
// Adjust only when top safeAreaInset is not equal to bottom
guard view.safeAreaInsets.top != view.safeAreaInsets.bottom else {
return
}
// Set additionalSafeAreaInsets to .zero before adjust to prevent accumulation
guard additionalSafeAreaInsets == .zero else {
additionalSafeAreaInsets = .zero
return
}
// Use gap between top and bottom safeAreaInset to adjust top inset
additionalSafeAreaInsets.top = view.safeAreaInsets.bottom - view.safeAreaInsets.top
}
}
您的里程可能会有所不同,但请尝试在
.insetsLayoutMarginsFromSafeArea = false
和
UIView
视图上设置
UIHostingController
。
从 iOS 16.4 开始,您可以尝试一下 safeAreaRegions:
hostingController.safeAreaRegions = []
您可以简单地使用
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)
}
}
}
结果: