SwiftUI: Overlay an SKScene over an SCNScene - swiftui

Setup:
For displaying 3D elements Scenekit is used. For 2D elements Spritekit.
SceneView() is used to display a scene.
An SCNScene and an SCNNode for the camera are given.
Goal:
Now I want to put a 2D overlay over the SCNScene. Previously this could be achieved using the overlaySKScene property of the SCNView. But now that in SwiftUI only the scene and camera are given I wonder how this can be achieved.
Thanks already in advance.

The SpriteKit overlay is not supported by SceneView in iOS 14.

If you want a SpriteKit scene layered on top of an SCNScene, you can do it with SwiftUI, like this:
struct MyView: View {
#State private var 2dscene = MyScene()
#State private var 3dScene = SCNScene(named: "MyFile")
var body: some View {
ZStack {
SceneView(scene: 3dScene)
SpriteView(scene: 2dscene)
}
}
}

Related

How to measure from the right to the button I placed at the top right in a SwiftUI

I added a button in the top right. But I want to measure the button from the right and from the top in a way that is compatible with all screens.
Are you looking to use GeometryReader?
var body: some View {
GeometryReader { geometry in
VStack {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}
}
}
The GeometryReader component is a view container that allows access to its size and position. In this way we can correctly align elements and equalize view sizes.

SwiftUI Image not switching to dark mode when created using UIImage

I have a simple SwiftUI view where I create a Image view using a UIImage. The issue is that the Image created with the UIImage is not switching to the dark or light mode when the color scheme changes. It works correctly only if the entire view is reloaded. It works as expected if I use create the image directly with the image name.
struct ContentView: View {
var body: some View {
HStack {
VStack {
Image("my-image")
Text("Image")
}
VStack {
Image(uiImage: UIImage(named: "my-image")!)
Text("Image+uiImage")
}
}
}
}
The image itself is in a xcasset catalog
result is this (note the right image not changing when the appearance changes but only after restarting the app)
This happens when the SwiftUI Image is initialized using an UIImage (seems to be a static initialization with no further observation of the original UIImage).
Workaround: The view can be informed to redraw manually, when using the colorScheme environment variable. Even when it seems to be unused in your later code.
This is pretty useful, if you need to consume UIImages from a shared UIKit/SwiftUI codebase.
In your example:
struct ContentView: View {
// This is required for the automatic re-creation of the view
// (and thus updating the Image with the appropriate appearance
// for the UIImage)
#Environment(\.colorScheme) private var colorScheme // <- view get's notified
var body: some View {
HStack {
VStack {
Image("my-image")
Text("Image")
}
VStack {
Image(uiImage: UIImage(named: "my-image")!)
Text("Image+uiImage")
}
}
}
}
Be aware that this might lead to unneeded redrawing, so try to use SwiftUI Images first, as #Jan said.
The workaround here is to use Image without using the UIImage constructor

How to get the current color of SwiftUI View?

I have a controller NSHostingController. And, I would like to know the current background color of the rootView.
However, it seems that the View does not have any property that allows the get the background color for that view.
#available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
}
In SwiftUI you manipulate with models not with views. So if you need to know current color, then put it in some model and pass it around, like
class ViewModel: ObservableObject {
#Published var backgroundColor: Color
}
struct SomeView: View {
#ObservedObject var vm: ViewModel
var body: some View {
Text("Some view")
.background(vm.backgroundColor)
}
}
so if you need to know somewhere current background color you ask it not in view but in view model where it is known and managed by you explicitly.

How do I get my UIView to correctly size for its content in SwiftUI?

I want to use the awesome MultiSegmentPicker written by Yonat Sharon in my SwiftUI View.
https://github.com/yonat/MultiSelectSegmentedControl
However, I don't fully understand the interaction between the UIViewRepresentable View and my SwiftUI View. How do I get the host view controller to shrink its height down to the size of the segmented control?
Here's the debugger view of the demo page - notice the blue area around the top bar:
The demo code doesn't give a lot of insight into the issue, it's just a call to the UIViewRepresentable view. I've simplified it to just one example here:
struct MultiSegmentPickerX: View {
#State private var selectedSegmentIndexes: IndexSet = []
var body: some View {
VStack(alignment: .center) {
Spacer()
MultiSegmentPicker(
selectedSegmentIndexes: $selectedSegmentIndexes,
items: ["First", "Second", "Third", "Done"]
)
}
}
}
Notice I have a VStack with a Spacer() before the control.
The desired behavior for this example would be that the bar with "First", "Second", etc. would be snug against the bottom of the screen. Instead the Host Controller holds onto all that space...
Do I need to use Geometry reader to solve this issue and shrink the height down. Or do I have to adjust something in the UIViewRepresentable View?
Any insights into bridging UIKit and SwiftUI are always appreciated...
Is this an easy fix anyone?
These did not solve my issue:
UIViewRepresentable content not updating
How do I make SwiftUI UIViewRepresentable view hug its content?
How do I size a UITextView to its content?
Cannot test it now, just by thoughts, try with fixed size as below
MultiSegmentPicker(
selectedSegmentIndexes: $selectedSegmentIndexes,
items: ["First", "Second", "Third", "Done"]
).fixedSize() // << here !!

Update UIViewRepresentable size from UIKit in SwiftUI

I'm embedding a view controller with variable-height UITextView inside a parent SwiftUI VStack and the view controller sizes it's frame to the whole screen between viewDidLoad and viewDidLayoutSubviews. The UITextView expands only to the size of the text inside itself and centers itself inside the parent view.
I'm trying to add this view controller in a VStack and have it behave externally like other SwiftUI components do - sized exactly to the content it contains - but it wants to be sized to the whole screen minus the other VStack elements.
I can get the correct size of the UITextView in didLayoutSubviews and pass it upwards to SwiftUI where it can be set properly - but where do I do that?
In the example screenshot below, the orange is the embedded UIView background, the green is the UITextView and the VStack looks like this:
VStack {
HighligherVC()
Text("Tap and drag to highlight")
.foregroundColor(.gray)
.font(.caption)
}
Without being able to see more of your code, it's slightly difficult to say what the best solution would be, but based purely on this part of your question...
I can get the correct size of the UITextView in didLayoutSubviews and pass it upwards to SwiftUI where it can be set properly - but where do I do that?
I would suggest that you pass a binding property to your view controller that can be set to the calculated text view height, meaning that the view that contains your VStack would have a #State property like this:
#State private var textViewHeight: CGFloat = 0
You would then declare a #Binding property on your HighlighterVC and add an initializer like this:
#Binding var textViewHeight: CGFloat
init(textViewHeight: Binding<CGFloat>) {
self._textViewHeight = textViewHeight
}
And then you would set textViewHeight to the calculated height in your didLayoutSubviews and add a .frame modifier to your HighlighterVC like this:
VStack {
HighlighterVC(textViewHeight: self.$textViewHeight)
.frame(height: self.textViewHeight)
Text("Tap and drag to highlight")
.foregroundColor(.gray)
.font(.caption)
}
Like I said at the beginning of my answer, this solution (that I believe would work, but since I can't test it, I'm not 100% certain) is based on your thoughts about what it is that you need. Without seeing more code, it's impossible for me to say if this is the best solution.
Add fixedSize may solve this.
.fixedSize(horizontal: false, vertical: true)