I have the following sample SwiftUI view
public struct TestView: View {
public var body: some View {
VStack(spacing: 4) {
ForEach(1..<5) { index in
Text("Row: \(index)")
Divider()
}
}
}
}
that is embedded in my AppKit app using NSHostingController
class TestViewController: NSHostingController<TestView> {
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: TestView())
}
}
I was expecting the NSHostingController to hug its SwiftUI view, instead what I get is a huge hosting controller view with the SwiftUI view centered like this (actual view is much larger, but cropped to save a bit space here):
I get the same result with NSHostingView. I also tried to make the SwiftUI view fixedSize or manually set its frame, to no avail.
Why is it not properly fitting the SwiftUI content and how can I fix it?
I ended up having to make sure the controllers NSHostingView does not create constraints for autoresizing masks.
class TestViewController: NSHostingController<TestView> {
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: TestView())
}
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
}
}
This seems to do the trick.
Related
If the NSPopover has its contentViewController set to some NSViewController that does not use SwiftUI directly on its view. For instance
final class ViewController: NSViewController {
private lazy var contentView = NSView()
override func loadView() {
view = contentView
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError()
}
}
let controller = ViewController()
popover.contentViewController = controller
It will be displayed centered.
However, if we simply change the line of the corresponding view:
private lazy var contentView = NSHostingView(rootView: Blah())
Where Blah is
struct Blah: View {
var body: some View {
ZStack {
Text("blah")
}
.frame(width: 400, height: 600, alignment: .center)
.background(Color.green)
}
}
You can see that the view is not centralized anymore. So, how can we let the NSViewController centralized in relation to the NSStatusItem item? (In the images those are one check icon)
If you give a close look at the images, you can see that the image with the white view has the popover arrow in the middle, while the other has not.
Is there a way to change the status bar color if using the new app life cycle?
I know that for UIKit life cycle there is the workaround where you create your own HostingController to overwrite the color.
But the new SwiftUI life cycle isn't using a UIHostingController at all.
Here is a possible workaround:
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView() // or any other loading view
.onAppear(perform: UIApplication.shared.switchHostingController)
}
}
}
class HostingController: UIHostingController<ContentView> {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
extension UIApplication {
func switchHostingController() {
windows.first?.rootViewController = HostingController(rootView: ContentView())
}
}
Two drawbacks I noticed:
the status bar style is switched in .onAppear - there might be a split second when you see the previous style
the ContentView will be created twice at the beginning
I have an app that uses storyboard, I need one of the tabs to be SwiftUI, how can I add it to my existing project?
I assume someone will need to know this at some point,
step 1.
Add a Hosting View Controller to your storyboard
step 2.
Create a root view controller relationship segue between your navigation controller/tab bar controller and the HostingView Controller
step 3.
create your SwiftUI Class
import SwiftUI
struct AnalyticsView: View {
var body: some View {
Text("Hello")
}
}
struct AnalyticsView_Previews: PreviewProvider {
static var previews: some View {
("Hello World")
}
}
step 4.
create a UIHostingController class and set the HostingViewController to that class in the class inspector
import UIKit
import SwiftUI
class AnalyticsVC: UIHostingController<AnalyticsView> {
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder, rootView: AnalyticsView())
}
}
step 5.
build your view controller from your SwiftUI view
The idea, I thought, was this: whatever is not provided yet in SwiftUI, one can get around using UIViewRepresentable. So I set out to make a UIViewRepresentable for TTTAttributedLabel.
But there is something basic missing in the API, or I am missing something basic: how can one set the "intrinsic content size" of the UIViewRepresentable?
Much like a SwiftUI Text, I would want my component to be able to set itself to a certain size considering the size of its container.
My first idea was to use intrinsic content sizes, so I created this:
class SizeRepresentableView: UILabel {
override init(frame: CGRect) {
super.init(frame: CGRect.zero)
text="hello"
backgroundColor = UIColor.systemYellow
}
override var intrinsicContentSize: CGSize {
return CGSize(width: 100, height: 100)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
struct SizeRepresentable: UIViewRepresentable {
func updateUIView(_ uiView: SizeRepresentableView, context: Context) {
}
typealias UIViewType = SizeRepresentableView
func makeUIView(context: UIViewRepresentableContext<SizeRepresentable>) -> SizeRepresentableView {
let v = SizeRepresentableView(frame: CGRect.zero)
return v
}
}
struct SizeRepresentableTest: View {
var body: some View {
VStack {
SizeRepresentable()
Spacer()
}
}
}
struct SizeRepresentable_Previews: PreviewProvider {
static var previews: some View {
SizeRepresentableTest()
}
}
but that does not work. The label takes up all the space, the spacer none. With a SwiftUI Text instead of my SizeRepresentable the label is neatly arranged on top and the spacer takes up all the space, as it should.
I can't seem to find any documentation about how to layout a UIViewRepresentable.
What is the way to solve this?
See: How does UIViewRepresentable size itself in relation to the UIKit control inside it?
struct SizeRepresentableTest: View {
var body: some View {
VStack {
SizeRepresentable().fixedSize()
Spacer()
}
}
}
I have created the subclass below to link my swiftui code to my storyboard. The goal is to have a vstack with text containers in it display inside a ContainerView. I am not sure if I am using the right class: NSViewController? I do not get any errors, but the code does not display how I want it to. Mostly, The swiftui does not display inside the window that shows up when I run the app.
import SwiftUI
class termu: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
#IBSegueAction func waka(_ coder: NSCoder) -> NSViewController? {
return NSHostingController(coder: coder, rootView: ContentView())
}
}
Here is the simplest MyViewController which you can specify for new controller scene in IB for your storyboard as custom class in Identity Inspector. (The controller scene and segue creation in IB as usual).
I selected Sheet segue for demo
import Cocoa
import SwiftUI
class MyViewController: NSHostingController<ContentView> {
#objc required dynamic init?(coder: NSCoder) {
super.init(coder: coder, rootView: ContentView())
}
}
struct ContentView: View { // This SwiftUI view is just for Demo
var body: some View {
VStack {
Text("I'm SwiftUI")
.font(.largeTitle)
.padding()
.background(Color.yellow)
}
.frame(width: 400, height: 200)
}
}