How to deal with the black area beyond the SKView() (In Swift Playground) - swift

I'm trying to use SpriteKit+SwiftUI in my work running in Swift Playground. Here are some of my codes
struct SwiftUI: View {
var body: some View {
Test()
}
}
struct Test: UIViewRepresentable {
func makeUIView(context: Context) -> SKView {
let sceneView = SKView()
let gameScene = GameScene()
gameScene.size = CGSize(width: 500, height: 600)
gameScene.scaleMode = .aspectFit
sceneView.presentScene(gameScene)
return sceneView
}
func updateUIView(_ uiView: SKView, context: Context) {
}
}
It runs well, but always have this awful Black area beyond my SKView like the image below.
Black area in the View.
I have tried to change the sceneView.backgroundcolor,or change the gameScene.size and sceneView.size but those just didn't work. 🤨
Thanks so much if you can give me some advice!

The issue is that the call to Test() also needs to have an explicit frame size set. Otherwise, the Test view takes up the entire available screen space, while the sceneView only takes up a portion of this space.
The example below is the same one that you posted, with an explicit frame set on Test() (and a dummy GameScene). This works in the playground, where the result is just a purple square with no black area beyond the view:
import SwiftUI
import SpriteKit
import PlaygroundSupport
let width: CGFloat = 500
let height: CGFloat = 500
struct ContentView: View {
var body: some View {
Test()
.frame(width: width, height: height)
}
}
struct Test: UIViewRepresentable {
func makeUIView(context: Context) -> SKView {
let sceneView = SKView()
let gameScene = GameScene()
gameScene.size = CGSize(width: width, height: height)
gameScene.scaleMode = .aspectFit
sceneView.presentScene(gameScene)
return sceneView
}
func updateUIView(_ uiView: SKView, context: Context) {
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
let node = SKShapeNode(rect: CGRect(x: 0, y: 0, width: size.width, height: size.height))
node.fillColor = .purple
addChild(node)
}
}
PlaygroundPage.current.setLiveView(ContentView())

Related

Move entity to center of nonAR view

I'm recreating the ARQuickLook controller in code. One of its behaviors is to move the model to the visible center when entering Obj mode. I've hacked the ARViewContainer of the default Xcode Augmented Reality App to demonstrate what I'm trying to do.
I think that moving the entity to 0,0,0 will generally not do the right thing because the world origin will be elsewhere. What I'm not clear on is how to specify the translation for entity.move() in the code. I'm assuming I'll need to raycast using a CGPoint describing view center to obtain the appropriate translation but I'm not sure about the details.
Thanks for any help with this.
struct ARViewContainer: UIViewRepresentable {
let arView = ARView(frame: .zero)
let boxAnchor = try! Experience.loadBox()
func makeUIView(context: Context) -> ARView {
arView.scene.anchors.append(boxAnchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
arView.environment.background = .color(.white)
arView.cameraMode = .nonAR
if let entity = boxAnchor.children.first {
let translation = SIMD3<Float>(x: 0, y: 0, z: 0 )
let transform = Transform(scale: .one, rotation: simd_quatf(), translation: translation)
entity.move(to: transform, relativeTo: nil, duration: 2, timingFunction: .easeInOut)
}
}
}
}

Making Lottie work with macOS and AppKit (without UIKit)

Works on Xcode 13.0 and MacOS 12.0
Was stuck and was unable to make Lottie work on an macOS native app to load the animation since the Lottie framework to link it to swiftUI on all the tutorial that I have looked online require the use of UIViewRepresentable, which is not available on a macOS only app. So after some work and tinkering, finally made it work using NSViewPresentable
struct LottieView: NSViewRepresentable {
//To allow the JSON file to be read and to display the animaton
//Allows link to SwiftUI from NSView()
let animationView = AnimationView()
var filename : String //The name of the file to be loaded
var speed: Double //The speed at which the animation should be played
var loop: LottieLoopMode //Whever the animation should loop
var heightView: Double
var widthView: Double
func makeNSView(context: NSViewRepresentableContext<LottieView>) -> NSView {
let view = NSView()
let animation = Animation.named(filename) //Loads the animation
animationView.animation = animation //Sets the animation
animationView.animationSpeed = CGFloat(speed) //Speed
animationView.contentMode = .scaleAspectFit //Aspect Ratio
animationView.loopMode = loop //Whever to loop
animationView.play() //Plays the animation
animationView
.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
view.setFrameSize(CGSize(width: 100, height: 100))
NSLayoutConstraint.activate([
animationView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
animationView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
animationView.heightAnchor.constraint(equalToConstant: heightView),
animationView.widthAnchor.constraint(equalToConstant: widthView)
])
return view //Necessary in order to conform to UIVieewRepresentable
}
func updateNSView(_ uiView: NSView, context: NSViewRepresentableContext<LottieView> ) {
}
}
**the only thing is that you have also to frame the view with the same width and height parameter passed to the NSViewRepresntable such that:
LottieView(filename: "cloudSyncAnimation", speed: 1.0, loop: .loop, heightView: 100, widthView: 100).frame(width: 100, height: 100, alignment: .center)
Hopefully someone can find this solution useful, probably not the most simple or elegant solution but works and I am fairly new to swift so any contribution would be appreciated
I was having the same problem with the size of the animation.
My guess was that the animationView was trying to first take the size of the original animation, so I removed the content resistance priority.
This is my implementation:
public struct LottieView: NSViewRepresentable{
public init(lottieFile: String, loopMode: LottieLoopMode = .loop, autostart: Bool = true, contentMode: LottieContentMode = LottieContentMode.scaleAspectFit) {
self.lottieFile = lottieFile
self.loopMode = loopMode
self.autostart = autostart
self.contentMode = contentMode
}
let lottieFile: String
let loopMode: LottieLoopMode
let autostart: Bool
let contentMode: LottieContentMode
let animationView = AnimationView()
public func makeNSView(context: Context) -> some NSView {
let theView = NSView()
animationView.animation = Animation.named(lottieFile)
animationView.contentMode = .scaleAspectFit
animationView.loopMode = loopMode
animationView.backgroundBehavior = .pauseAndRestore
if self.autostart{
animationView.play()
}
theView.addSubview(animationView)
animationView.translatesAutoresizingMaskIntoConstraints = false
animationView.heightAnchor.constraint(equalTo: theView.heightAnchor).isActive = true
animationView.widthAnchor.constraint(equalTo: theView.widthAnchor).isActive = true
animationView.leadingAnchor.constraint(equalTo: theView.leadingAnchor).isActive = true
animationView.trailingAnchor.constraint(equalTo: theView.trailingAnchor).isActive = true
animationView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
animationView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return theView
}
func pause(){
animationView.pause()
}
func play(){
animationView.play()
}
func stop(){
animationView.stop()
}
public func updateNSView(_ nsView: NSViewType, context: Context) {
}
}
No need to have a wrapper or set a fixed size.

Macaw SVG .scaleAspectFit with SwiftUI

Using Macaw master branch (for Swift Package Manager reasons).
The goal is to just get SVGView to fit inside the frame. The border I draw around SVGViewRepresentable correctly confirms the desired frame size. But .scaleAspectFit just isn't giving the result of scaling up/down SVGView to fit the frame.
struct SVGViewRepresentable: UIViewRepresentable {
var svgName: String
let size: CGSize
func makeUIView(context: Context) -> SVGView {
let node = try! SVGParser.parse(resource: svgName)
let svgView = SVGView(node: node, frame: CGRect(origin: CGPoint.zero, size: size))
svgView.backgroundColor = UIColor.clear
svgView.layer.borderWidth = 1.0
svgView.layer.borderColor = UIColor.blue.cgColor
svgView.contentMode = .scaleAspectFit
return svgView
}
func updateUIView(_ uiView: SVGView, context: Context) {
}
}
struct MacawSVGView: View {
var body: some View {
GeometryReader { (geometry: GeometryProxy) in
SVGViewRepresentable(svgName: "Tiger", size: geometry.size)
.frame(width: geometry.size.width, height: geometry.size.height)
.border(Color.red)
}
}
}

Programmatically Setting AutoresizingMask to NSView Object

I wonder why it always fails whenever I try to set the autoresizingMask to an NSView object programmatically? In the following lines of code, I have a tiny, blue NSVIew object (topLeftView) set at the top-left corner of its parent container.
class ViewController: NSViewController {
// MARK: - IBOutlet
#IBOutlet weak var panView: PanView!
override func viewDidLoad() {
super.viewDidLoad()
makeImageContainer(point: CGPoint.zero)
}
func makeViewContainer(point: CGPoint) {
let myView = NSView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 150.0, height: 150.0))
myView.wantsLayer = true
if let myLayer = myView.layer {
myLayer.backgroundColor = NSColor.orange.cgColor
}
panView.addSubview(myView) // panView is an IBOutlet-connected `NSView` object.
/* tiny, blue `NSView` object */
let topLeftView = NSView(frame: CGRect(origin: CGPoint(x: 0.0, y: 150.0 - 6.0), size: CGSize(width: 6.0, height: 6.0)))
topLeftView.wantsLayer = true
topLeftView.autoresizingMask = [.minXMargin, .maxYMargin]
if let layer = topLeftView.layer {
layer.backgroundColor = NSColor.blue.cgColor
}
myView.addSubview(topLeftView)
}
}
So it has minXMargin and maxYMargin autoresizingMasks.
Now, I want to click on a push button to resize this orange NSView container object to see if the tiny, blue NSView object will stick at the top-left corner.
#IBAction func testClicked(_ sender: NSButton) {
for i in 0..<panView.subviews.count {
let subView = panView.subviews[i]
if i == 4 {
subView.setFrameSize(CGSize(width: 200.0, height: 200.0))
}
}
}
And the tiny guy has moved by 50 points to the right and 50 points to the bottom. What am I doing wrong? Thanks.

How to add 3D shapes in Swift UI?

I want to know how to add 3D shapes (eg. Sphere) in swift UI
I have tried adding a scnscene and using that in swift UI but I get a error message
//SceneKit
class myscene: SCNScene{
override init(){
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder: ) has not been implemented")
}
}
//Swift UI
struct ContentView: View {
var body: some View {
let sphere = SCNSphere(radius: 2.0)
sphere.firstMaterial?.diffuse.contents = UIColor.blue
let spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
}
}
The error message is on var body: some View { line and reads as follows :
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Please help me with this problem......
Here is simplest code to demo how to setup SceneKit scene with your sphere. Hope it helps.
import SwiftUI
import SceneKit
struct SceneKitView: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.black
let sphere = SCNSphere(radius: 2.0)
sphere.firstMaterial?.diffuse.contents = UIColor.blue
let spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
sceneView.scene?.rootNode.addChildNode(spherenode)
return sceneView
}
func updateUIView(_ uiView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
}
typealias UIViewType = SCNView
}
struct DemoSceneKit: View {
var body: some View {
SceneKitView()
}
}
struct DemoSceneKit_Previews: PreviewProvider {
static var previews: some View {
DemoSceneKit()
}
}