Embed a SwiftUI view in an AppKit view - swift

I have a SwiftUI view MySwiftUIView:
import SwiftUI
struct MySwiftUIView: View {
var body: some View {
Text("Hello, World!")
}
}
I want to use it as part of an AppKit view. I tried the following code:
import Cocoa
import SwiftUI
class MyViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview( NSHostingView(rootView: MySwiftUIView()) )
}
}
with the corresponding storyboard:
After the code is built, the result is an empty window:
What I want is this:
How should I make this happen?

You setup subview programmatically, so constraints are on your responsibility, no exception for SwiftUI.
Here is correct variant (tested with Xcode 11.4):
override func viewDidLoad() {
super.viewDidLoad()
let myView = NSHostingView(rootView: MySwiftUIView())
myView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(myView)
myView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
myView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}

Related

Drawing issues using SwiftUI view as a PrimaryView in uisplitviewcontroller

I am trying to use SwiftUI View as a primary view in a UISplitViewController.
I have setup and added my PrimaryViewController to the NavigationController in my SplitviewController, in Storyboard.
mySwiftUI view code is as follows:
struct Tile_SwiftUI: View {
var body: some View {
RoundedRectangle(cornerRadius: 15)
.fill( Color.red )
.frame(height: 100)
.padding()
}
}
And my PrimaryViewController setup is as follows:
import UIKit
import SwiftUI
class PrimaryViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testVC = UIHostingController(rootView: Tile_SwiftUI() )
self.view.backgroundColor = UIColor.white
testVC.view.backgroundColor = UIColor.blue
addChild(testVC)
view.addSubview(testVC.view)
testVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
testVC.view.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor ),
testVC.view.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor ),
testVC.view.topAnchor.constraint(equalTo: view.topAnchor ),
testVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor ),
])
testVC.didMove(toParent: self)
}
}
It all works as expected on the iPhone and iPad landscape orientation, but on portrait orientation the primaryViewController gets an odd overlay. Please see attached images.
Here is also a link to the full project:
https://www.sticklets.co/code/SplitViewController_SwiftUI_bug.zip
Any ideas what is causing this?

Hiding bottom line on navigation controller in SwiftUI

How do I hide this bottom bar on a UINavigationController with SwiftUI? So far I have found only solutions for UIKit, but nothing for SwiftUI.
Look at the accepted answer: SwiftUI Remove NavigationBar Bottom Border
Before:
After:
import SwiftUI
struct TestView: View {
init(){
let appearance = UINavigationBarAppearance()
appearance.shadowColor = .clear
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
var body: some View {
NavigationView{
ScrollView{
ForEach(0 ..< 20){ num in
Text("Num - \(num)")
.padding()
}
}
.navigationTitle("Learn")
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
I had the same issue when using a UIHostingController. So I ended up adding a child UIHostingController to a UIViewController and setting the shadow that way.
#IBOutlet weak var theContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = UIColor(Color("navbackground"))
appearance.shadowColor = .clear
self.navigationController?.navigationBar.scrollEdgeAppearance = appearance
self.navigationController?.navigationBar.standardAppearance = appearance
let childView = UIHostingController(rootView: SettingsView())
addChild(childView)
childView.view.frame = theContainer.bounds
theContainer.addSubview(childView.view)
childView.didMove(toParent: self)
}

SwiftUI: Changing values in a Class

This is a menubar macOS app. I'm trying to change the dropdown window size with a button from the content view. This is my attempt so far.
struct ContentView: View {
#ObservedObject var app = AppDelegate()
var body: some View {
VStack{
Text("Hello World")
.padding(.top, 10)
Button {
biggerwindow()
} label: {
Image("tv")
}
}
}
func biggerwindow(){
app.popover.contentSize = NSSize(width: 800, height: 800 )
}
}
Nothing happens when clicking on the button.
This is the AppDelegate class that contains the default values
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
var popover = NSPopover.init()
var statusBar: StatusBarController?
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the contents
let contentView = ContentView()
// Set the SwiftUI's ContentView to the Popover's ContentViewController
popover.contentViewController = MainViewController()
//default size of the menu bar window
popover.contentSize = NSSize(width: 360, height: 360 )
popover.contentViewController?.view = NSHostingView(rootView: contentView)
// Create the Status Bar Item with the Popover
statusBar = StatusBarController.init(popover)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
The problem is here:
#ObservedObject var app = AppDelegate()
You're creating a new instance of AppDelegate here, with its own instance of popover. You need to use the existing instance of AppDelegate that was created at app launch. You can access the existing instance through the global NSApplication.shared object.
struct ContentView: View {
var app: AppDelegate { NSApplication.shared.delegate as! AppDelegate }

How to center NSPopover when using SwiftUI?

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.

What is the correct subclass to link storyboard and swiftUI?

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)
}
}