UIViewControllerRepresentable 未正确占用 space
UIViewControllerRepresentable not correctly taking up space
我正在尝试在 SwiftUI 视图中使用自定义 UIViewController
。我设置了一个 UIViewControllerRepresentable
class,它在 makeUIViewController
方法中创建了 UIViewController
。这将创建 UIViewController
并显示按钮,但是 UIViewControllerRepresentable
不会占用任何 space.
我尝试使用 UIImagePickerController
而不是我的自定义控制器,并且大小正确。我让我的控制器占用 space 的唯一方法是在我的 SwiftUI 视图中的 UIViewControllerRepresentable
上设置一个固定框架,我绝对不想这样做。
注意:我确实需要使用 UIViewController
,因为我正尝试在 SwiftUI 中实现 UIMenuController
。除了我遇到的大小调整不正确的问题外,我所有这些都可以正常工作。
这是我的代码:
struct ViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MenuViewController {
let controller = MenuViewController()
return controller
}
func updateUIViewController(_ uiViewController: MenuViewController, context: Context) {
}
}
class MenuViewController: UIViewController {
override func viewDidLoad() {
let button = UIButton()
button.setTitle("Test button", for: .normal)
button.setTitleColor(.red, for: .normal)
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
button.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
}
我的 SwiftUI 视图:
struct ClientView: View {
var body: some View {
VStack(spacing: 0) {
EntityViewItem(copyValue: "copy value", label: {
Text("Name")
}, content: {
Text("Random name")
})
.border(Color.green)
ViewControllerRepresentable()
.border(Color.red)
EntityViewItem(copyValue: "copy value", label: {
Text("Route")
}, content: {
HStack(alignment: .center) {
Text("Random route name")
}
})
.border(Color.blue)
}
}
}
截图:
我对 UIKit 没有太多经验 - 我唯一的经验是编写 UIKit 视图以在 SwiftUI 中使用。这个问题很可能与我缺乏 UIKit 知识有关。
提前致谢!
编辑:
这是 EntityViewItem
的代码。我还将提供 ClientView
所在的容器视图 - EntityView
.
我还清理了其余代码并将对 Entity
的引用替换为硬编码值。
struct EntityViewItem<Label: View, Content: View>: View {
var copyValue: String
var label: Label
var content: Content
var action: (() -> Void)?
init(copyValue: String, @ViewBuilder label: () -> Label, @ViewBuilder content: () -> Content, action: (() -> Void)? = nil) {
self.copyValue = copyValue
self.label = label()
self.content = content()
self.action = action
}
var body: some View {
VStack(alignment: .leading, spacing: 2) {
label
.opacity(0.6)
content
.onTapGesture {
guard let unwrappedAction = action else {
return
}
unwrappedAction()
}
.contextMenu {
Button(action: {
UIPasteboard.general.string = copyValue
}) {
Text("Copy to clipboard")
Image(systemName: "doc.on.doc")
}
}
}
.padding([.top, .leading, .trailing])
.frame(maxWidth: .infinity, alignment: .leading)
}
}
ClientView
的容器:
struct EntityView: View {
let headerHeight: CGFloat = 56
var body: some View {
ZStack {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
Color.clear.frame(
height: headerHeight
)
ClientView()
}
}
VStack(spacing: 0) {
HStack {
Button(action: {
}, label: {
Text("Back")
})
Spacer()
Text("An entity name")
.lineLimit(1)
.minimumScaleFactor(0.5)
Spacer()
Color.clear
.frame(width: 24, height: 0)
}
.frame(height: headerHeight)
.padding(.leading)
.padding(.trailing)
.background(
Color.white
.ignoresSafeArea()
.opacity(0.95)
)
Spacer()
}
}
}
}
正如@jnpdx 提到的,您需要通过框架提供明确的大小,以便可表示对象可见,因为它与其他视图一起嵌套在 VStack 中。
如果您有使用 UIViewController 的特定原因,请提供明确的框架或创建 SwiftUI 视图。
struct ClientView: View {
var body: some View {
VStack(spacing: 0) {
EntityViewItem(copyValue: "copy value", label: {
Text("Name")
}, content: {
Text("Random name")
})
.border(Color.green)
ViewControllerRepresentable()
.border(Color.red)
.frame(height: 100.0)
EntityViewItem(copyValue: "copy value", label: {
Text("Route")
}, content: {
HStack(alignment: .center) {
Text("Random route name")
}
})
.border(Color.blue)
}
}
}
非常感谢@udbhateja 和@jnpdx 的帮助。这就解释了为什么 UIViewControllerRepresentable
在 ScrollView
中压缩它的框架。我确实最终找到了解决我的问题的方法,其中涉及在 UIViewControllerRepresentable
上设置固定高度。基本上,我使用 PreferenceKey
找到 SwiftUI 视图的高度,并设置 UIViewControllerRepresentable
的框架来匹配它。
如果有人遇到同样的问题,这是我的代码:
struct EntityViewItem<Label: View, Content: View>: View {
var copyValue: String
var label: Label
var content: Content
var action: (() -> Void)?
@State var height: CGFloat = 0
init(copyValue: String, @ViewBuilder label: () -> Label, @ViewBuilder content: () -> Content, action: (() -> Void)? = nil) {
self.copyValue = copyValue
self.label = label()
self.content = content()
self.action = action
}
var body: some View {
ViewControllerRepresentable(copyValue: copyValue) {
SizingView(height: $height) { // This calculates the height of the SwiftUI view and sets the binding
VStack(alignment: .leading, spacing: 2) {
// Content
}
.padding([.leading, .trailing])
.padding(.top, 10)
.padding(.bottom, 10)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.frame(height: height) // Here I set the height to the value returned from the SizingView
}
}
以及 SizingView
的代码:
struct SizingView<T: View>: View {
let view: T
@Binding var height: CGFloat
init(height: Binding<CGFloat>, @ViewBuilder view: () -> T) {
self.view = view()
self._height = height
}
var body: some View {
view.background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self) { preferences in
height = preferences.height
}
}
func size(with view: T, geometry: GeometryProxy) -> T {
height = geometry.size.height
return view
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
完成后,我的 UIMenuController
就可以正常使用了。这是很多代码(如果 SwiftUI 中存在此功能,我可能不得不编写 5 行代码),但它运行良好。如果有人想要代码,请评论,我会分享。
这是最终产品的图片:
如果其他人正在尝试寻找更简单的解决方案,那么可以使用任何视图控制器并调整大小以适合其内容:
struct ViewControllerContainer: UIViewControllerRepresentable {
let content: UIViewController
class Coordinator: NSObject {
var parent: ViewControllerContainer
init(_ pageViewController: ViewControllerContainer) {
self.parent = pageViewController
}
}
init(_ content: UIViewController) {
self.content = content
}
func makeCoordinator() -> Coordinator {
Coordinator(ViewControllerContainer(content))
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ViewControllerContainer>) -> UIViewController {
let size = content.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
content.preferredContentSize = size
return content
}
func updateUIViewController(_ uiViewController: UIViewController,
context: UIViewControllerRepresentableContext<ViewControllerContainer>) {
}
}
然后,当您在 SwiftUI 中使用它时,请务必调用 .fixedSize()
:
struct MainView: View {
var body: some View {
VStack(spacing: 0) {
ViewControllerContainer(MenuViewController())
.fixedSize()
}
}
}
对于寻找最简单解决方案的人来说,@Edudjr 的回答中有几行:
let size = content.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
content.preferredContentSize = size
只需将其添加到您的 makeUIViewController 中即可!
我正在尝试在 SwiftUI 视图中使用自定义 UIViewController
。我设置了一个 UIViewControllerRepresentable
class,它在 makeUIViewController
方法中创建了 UIViewController
。这将创建 UIViewController
并显示按钮,但是 UIViewControllerRepresentable
不会占用任何 space.
我尝试使用 UIImagePickerController
而不是我的自定义控制器,并且大小正确。我让我的控制器占用 space 的唯一方法是在我的 SwiftUI 视图中的 UIViewControllerRepresentable
上设置一个固定框架,我绝对不想这样做。
注意:我确实需要使用 UIViewController
,因为我正尝试在 SwiftUI 中实现 UIMenuController
。除了我遇到的大小调整不正确的问题外,我所有这些都可以正常工作。
这是我的代码:
struct ViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MenuViewController {
let controller = MenuViewController()
return controller
}
func updateUIViewController(_ uiViewController: MenuViewController, context: Context) {
}
}
class MenuViewController: UIViewController {
override func viewDidLoad() {
let button = UIButton()
button.setTitle("Test button", for: .normal)
button.setTitleColor(.red, for: .normal)
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
button.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
}
我的 SwiftUI 视图:
struct ClientView: View {
var body: some View {
VStack(spacing: 0) {
EntityViewItem(copyValue: "copy value", label: {
Text("Name")
}, content: {
Text("Random name")
})
.border(Color.green)
ViewControllerRepresentable()
.border(Color.red)
EntityViewItem(copyValue: "copy value", label: {
Text("Route")
}, content: {
HStack(alignment: .center) {
Text("Random route name")
}
})
.border(Color.blue)
}
}
}
截图:
我对 UIKit 没有太多经验 - 我唯一的经验是编写 UIKit 视图以在 SwiftUI 中使用。这个问题很可能与我缺乏 UIKit 知识有关。
提前致谢!
编辑:
这是 EntityViewItem
的代码。我还将提供 ClientView
所在的容器视图 - EntityView
.
我还清理了其余代码并将对 Entity
的引用替换为硬编码值。
struct EntityViewItem<Label: View, Content: View>: View {
var copyValue: String
var label: Label
var content: Content
var action: (() -> Void)?
init(copyValue: String, @ViewBuilder label: () -> Label, @ViewBuilder content: () -> Content, action: (() -> Void)? = nil) {
self.copyValue = copyValue
self.label = label()
self.content = content()
self.action = action
}
var body: some View {
VStack(alignment: .leading, spacing: 2) {
label
.opacity(0.6)
content
.onTapGesture {
guard let unwrappedAction = action else {
return
}
unwrappedAction()
}
.contextMenu {
Button(action: {
UIPasteboard.general.string = copyValue
}) {
Text("Copy to clipboard")
Image(systemName: "doc.on.doc")
}
}
}
.padding([.top, .leading, .trailing])
.frame(maxWidth: .infinity, alignment: .leading)
}
}
ClientView
的容器:
struct EntityView: View {
let headerHeight: CGFloat = 56
var body: some View {
ZStack {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
Color.clear.frame(
height: headerHeight
)
ClientView()
}
}
VStack(spacing: 0) {
HStack {
Button(action: {
}, label: {
Text("Back")
})
Spacer()
Text("An entity name")
.lineLimit(1)
.minimumScaleFactor(0.5)
Spacer()
Color.clear
.frame(width: 24, height: 0)
}
.frame(height: headerHeight)
.padding(.leading)
.padding(.trailing)
.background(
Color.white
.ignoresSafeArea()
.opacity(0.95)
)
Spacer()
}
}
}
}
正如@jnpdx 提到的,您需要通过框架提供明确的大小,以便可表示对象可见,因为它与其他视图一起嵌套在 VStack 中。
如果您有使用 UIViewController 的特定原因,请提供明确的框架或创建 SwiftUI 视图。
struct ClientView: View {
var body: some View {
VStack(spacing: 0) {
EntityViewItem(copyValue: "copy value", label: {
Text("Name")
}, content: {
Text("Random name")
})
.border(Color.green)
ViewControllerRepresentable()
.border(Color.red)
.frame(height: 100.0)
EntityViewItem(copyValue: "copy value", label: {
Text("Route")
}, content: {
HStack(alignment: .center) {
Text("Random route name")
}
})
.border(Color.blue)
}
}
}
非常感谢@udbhateja 和@jnpdx 的帮助。这就解释了为什么 UIViewControllerRepresentable
在 ScrollView
中压缩它的框架。我确实最终找到了解决我的问题的方法,其中涉及在 UIViewControllerRepresentable
上设置固定高度。基本上,我使用 PreferenceKey
找到 SwiftUI 视图的高度,并设置 UIViewControllerRepresentable
的框架来匹配它。
如果有人遇到同样的问题,这是我的代码:
struct EntityViewItem<Label: View, Content: View>: View {
var copyValue: String
var label: Label
var content: Content
var action: (() -> Void)?
@State var height: CGFloat = 0
init(copyValue: String, @ViewBuilder label: () -> Label, @ViewBuilder content: () -> Content, action: (() -> Void)? = nil) {
self.copyValue = copyValue
self.label = label()
self.content = content()
self.action = action
}
var body: some View {
ViewControllerRepresentable(copyValue: copyValue) {
SizingView(height: $height) { // This calculates the height of the SwiftUI view and sets the binding
VStack(alignment: .leading, spacing: 2) {
// Content
}
.padding([.leading, .trailing])
.padding(.top, 10)
.padding(.bottom, 10)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.frame(height: height) // Here I set the height to the value returned from the SizingView
}
}
以及 SizingView
的代码:
struct SizingView<T: View>: View {
let view: T
@Binding var height: CGFloat
init(height: Binding<CGFloat>, @ViewBuilder view: () -> T) {
self.view = view()
self._height = height
}
var body: some View {
view.background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self) { preferences in
height = preferences.height
}
}
func size(with view: T, geometry: GeometryProxy) -> T {
height = geometry.size.height
return view
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
完成后,我的 UIMenuController
就可以正常使用了。这是很多代码(如果 SwiftUI 中存在此功能,我可能不得不编写 5 行代码),但它运行良好。如果有人想要代码,请评论,我会分享。
这是最终产品的图片:
如果其他人正在尝试寻找更简单的解决方案,那么可以使用任何视图控制器并调整大小以适合其内容:
struct ViewControllerContainer: UIViewControllerRepresentable {
let content: UIViewController
class Coordinator: NSObject {
var parent: ViewControllerContainer
init(_ pageViewController: ViewControllerContainer) {
self.parent = pageViewController
}
}
init(_ content: UIViewController) {
self.content = content
}
func makeCoordinator() -> Coordinator {
Coordinator(ViewControllerContainer(content))
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ViewControllerContainer>) -> UIViewController {
let size = content.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
content.preferredContentSize = size
return content
}
func updateUIViewController(_ uiViewController: UIViewController,
context: UIViewControllerRepresentableContext<ViewControllerContainer>) {
}
}
然后,当您在 SwiftUI 中使用它时,请务必调用 .fixedSize()
:
struct MainView: View {
var body: some View {
VStack(spacing: 0) {
ViewControllerContainer(MenuViewController())
.fixedSize()
}
}
}
对于寻找最简单解决方案的人来说,@Edudjr 的回答中有几行:
let size = content.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
content.preferredContentSize = size
只需将其添加到您的 makeUIViewController 中即可!