SwiftUI: HitTest on Scenekit - swiftui

Goal: SceneKit hit test with SwiftUI (instead of UIKit)
Problem: When I embed the default ship scene on a SwiftUI "UIViewRepresentable", the example handleTap function doesn't work. and I get his error:
"Argument of '#selector' refers to instance method 'handleTap' that is not exposed to Objective-C"
How an I create a hit test, and pass data to another SwiftUI view?
import SwiftUI
import SceneKit
var handleTap: (() -> Void)
struct ScenekitView : UIViewRepresentable {
let scene = SCNScene(named: "ship.scn")!
func makeUIView(context: Context) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// retrieve the SCNView
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = SCNView()
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// get material for selected geometry element
let material = result.node.geometry!.firstMaterial
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material?.emission.contents = UIColor.black
SCNTransaction.commit()
}
material?.emission.contents = UIColor.green
SCNTransaction.commit()
}
}
}
#if DEBUG
struct ScenekitView_Previews : PreviewProvider {
static var previews: some View {
ScenekitView()
}
}
#endif

Just hit this issue myself and finally found a solution: make a dummy struct that pulls from a class that actually holds your SCNView.
This works for me:
struct ScenekitView : UIViewRepresentable {
let scenekitClass = ScenekitClass()
func makeUIView(context: Context) -> SCNView {
return scenekitClass.view
}
func updateUIView(_ scnView: SCNView, context: Context) {
// your update UI view contents look like they can all be done in the initial creation
}
}
class ScenekitClass {
let view = SCNView()
let scene = SCNScene(named: "ship.scn")!
init() {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// attach the scene
view.scene = scene
// allows the user to manipulate the camera
view.allowsCameraControl = true
// show statistics such as fps and timing information
view.showsStatistics = true
// configure the view
view.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
view.addGestureRecognizer(tapGesture)
}
#objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// check what nodes are tapped
let p = gestureRecognize.location(in: view)
let hitResults = view.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// get material for selected geometry element
let material = result.node.geometry!.firstMaterial
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material?.emission.contents = UIColor.black
SCNTransaction.commit()
}
material?.emission.contents = UIColor.green
SCNTransaction.commit()
}
}
}
Based on this question.

For whatever reason, the SwiftUI SceneView does not conform to the SCNSceneRenderer protocol. If it did, then it would not be necessary to make use of a UIViewRepresentable (or NSViewRepresentable for macOS) view.
I have a complete example app, for macOS, here:
https://github.com/Thunor/HitTestApp

SceneView has a delegate argument. You can use a SCNSceneRenderDelegate to capture the SCNSceneRenderer and use it for hit testing. Here's an example:
import SwiftUI
import SceneKit
import Foundation
class RenderDelegate: NSObject, SCNSceneRendererDelegate {
// dummy render delegate to capture renderer
var lastRenderer: SCNSceneRenderer!
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
// store the renderer for hit testing
lastRenderer = renderer
}
}
class Model: ObservableObject {
let scene = SCNScene(named: "scene.usdz")!
let renderDelegate = RenderDelegate()
}
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
SceneView(scene: model.scene, options: [.allowsCameraControl, .autoenablesDefaultLighting], delegate: model.renderDelegate)
.gesture(
SpatialTapGesture(count: 1)
.onEnded(){ event in
// hit test
guard let renderer = model.renderDelegate.lastRenderer else { return }
let hits = renderer.hitTest(event.location, options: nil)
if let tappedNode = hits.first?.node {
// do something
}
}
)
}
}

Related

Realitykit / ArKit make a collision only between 2 objects

I have a ball and a cylinder. When i tap on the ball it moves in forward position. When the ball hits the cylinder they collide and the cylinder moves and changes color. Thats working fine.
I have 2 problems:
When i start the app the cylinder is constantly colliding and changes color immediately, so even when the ball is not near it. It looks like its colliding with something else.
The second thing is, there will be more cylinders in the scene, how can i make a collision event only between 2 objects. Lets say there are 2 cylinders in the scene how can ik set a filter or group.
import SwiftUI
import RealityKit
import ARKit
import FocusEntity
import Combine
struct ContentView : View {
var body: some View {
ARViewContainer()
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let view = ARView()
let session = view.session
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
session.run(config)
let coachingOverlay = ARCoachingOverlayView()
coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
coachingOverlay.session = session
coachingOverlay.goal = .horizontalPlane
view.addSubview(coachingOverlay)
context.coordinator.view = view
session.delegate = context.coordinator
view.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
return view
}
func updateUIView(_ uiView: ARView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
//coordinator class
class Coordinator: NSObject,ARSessionDelegate {
weak var view: ARView?
var focusEntity: FocusEntity?
#Published var sceneIsPlaced:Bool = false //is de scene reeds geplaats na openen app
#Published var subscriptions: [AnyCancellable] = []
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
guard let view = self.view else { return }
self.focusEntity = FocusEntity(on: view, style: .classic(color: .yellow))
}
//na tap op het scherm, wat te doen -> creeer locatie en plaats het object
#objc func handleTap(recognizer: UITapGestureRecognizer) {
guard let view = self.view, let focusEntity = self.focusEntity else { return }
//tap location van de tap gesture
let tapLocation = recognizer.location(in: self.view)
//Create Anchor
let anchor = AnchorEntity()
let importModel = try! Entity.load(named: "cilinder") //alle objects!
importModel.position = focusEntity.position
importModel.scale = SIMD3(repeating: 0.5)
//The cylinder
let kolomMiddleModel = importModel.findEntity(named: "cilinder")!.children[0] as! (ModelEntity & HasPhysicsBody & HasCollision)
let materialKolomMiddle = SimpleMaterial(color: .yellow, isMetallic: true)
kolomMiddleModel.model?.materials = [materialKolomMiddle]
kolomMiddleModel.generateCollisionShapes(recursive: false)
let physics = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
kolomMiddleModel.components.set(physics)
//MAKE A BALL
let materialsBall = SimpleMaterial(color: .red, isMetallic: true)
let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.1),
materials: [materialsBall])
as (Entity & HasPhysicsBody & HasCollision)
ballModel.position = [-0.1, -1.0,-0.1]
ballModel.generateCollisionShapes(recursive: true)
ballModel.name = "ballModel"
//LIGHTS --> let op voor de shadow moet de plane van occlusion material met dynamical lightning op tue
let directionalLight = DirectionalLight()
directionalLight.light.color = .white
directionalLight.light.intensity = 4000
directionalLight.light.isRealWorldProxy = true
directionalLight.shadow?.maximumDistance = 1.5
directionalLight.shadow?.depthBias = 7.0
directionalLight.orientation = simd_quatf(angle: .pi/1.5, axis: [0,1,0])
//maak een anchor voor het licht
let lightAnchor = AnchorEntity(world: [0, 0, 2.5])
lightAnchor.addChild(directionalLight)
//COLLISION EVENTS
DispatchQueue.main.async {
//collision of kolomModel
view.scene.subscribe(to: CollisionEvents.Began.self,
on: kolomMiddleModel) { _ in
print("Collision kolomModel detected!")
let material = SimpleMaterial(color: .red, isMetallic: true)
kolomMiddleModel.model?.materials = [material]
}.store(in: &self.subscriptions)
}
// view.installGestures(for: ballModel)
//
//PLACE SCENE IF NOT PLACED ALREADY
if !sceneIsPlaced {
//ADD MODELS TO ANCHOR
anchor.addChild(importModel)
anchor.addChild(ballModel)
anchor.addChild(lightAnchor)
view.scene.addAnchor(anchor)
sceneIsPlaced = true
//If the scene is already placed get the taplocation
}else{
if let locationEntity = view.entity(at: tapLocation) {
let entityTapped = locationEntity as! ModelEntity
//MAKE THE BALL GO FORWARD
print("entity tapped \(entityTapped)")
entityTapped.physicsBody = .init()
entityTapped.physicsBody?.mode = .kinematic
entityTapped.physicsMotion = PhysicsMotionComponent(linearVelocity: [0, 0, -0.5],
angularVelocity: [1, 3, 5])
}
}
}
}
}
Here is an example of two objects in RealityKit that can only collide with each other:
let sphere = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial()])
sphere.collision = CollisionComponent(shapes: [ShapeResource.generateSphere(radius: 0.1)])
sphere.collision?.filter = CollisionFilter(group: 1 << 2, categories: .default, mask: .default)
let cube = ModelEntity(mesh: .generateBox(size: [0.1, 0.1, 0.1]), materials: [SimpleMaterial()])
cube.collision = CollisionComponent(shapes: [ShapeResource.generateBox(size: [0.1, 0.1, 0.1])])
cube.collision?.filter = CollisionFilter(group: 1 << 1, categories: .default, mask: .default)
sphere.collision?.filter.collisionGroup = 1 << 1
cube.collision?.filter.collisionGroup = 1 << 2
arView.scene.addAnchor(sphere)
arView.scene.addAnchor(cube)
This code creates two objects: a sphere and a cube. Each object is given a CollisionComponent with a corresponding ShapeResource and CollisionFilter. The CollisionFilter for each object specifies that only objects in a different collisionGroup can collide with it. As a result, the sphere and cube can only collide with each other, not with other objects in the scene.
See more information about CollisionGroups here:
https://developer.apple.com/documentation/realitykit/collisionfilter

SwiftUI - NSFontPanel and Color Picker

I am trying to get NSFontPanel/NSFontManager to work in a SwiftUI Document Template app. I have the following which is a customize version of one I found on GitHub. This lets me pick the size, face, style, etc.
Interestingly, a color picker is included in the FontPanel. The documentation doesn't seem to say this. Is this something new?
Anyway, I would like to either be able to use the color picker to let the user select a color, or if not I would like to hide the color picker - at is not "critical" to this application. I am using this to allow customization of text in a sidebar, so color is nice, but not necessary. Currently the Font settings are working, but the color selection displays, and let you pick on, but it always returns System Color.
Any help would be appreciated.
NOTE: I didn't include the FontPickerDelegate, it just calls this:
public struct FontPicker: View{
let labelString: String
#Binding var font: NSFont
#State var fontPickerDelegate: FontPickerDelegate?
public init(_ label: String, selection: Binding<NSFont>) {
self.labelString = label
self._font = selection
}
let fontManager = NSFontManager.shared
let fontPanel = NSFontPanel.shared
#AppStorage("setSidebarFont") var setSidebarFont = "System"
#AppStorage("setSidebarFontSize") var setSidebarFontSize = 24
#AppStorage("setSidebarFontColor") var setSidebarFontColor = "gray"
public var body: some View {
HStack {
Text(labelString)
Button {
if fontPanel.isVisible {
fontPanel.orderOut(nil)
return
}
self.fontPickerDelegate = FontPickerDelegate(self)
fontManager.target = self.fontPickerDelegate
fontManager.action = #selector(fontPickerDelegate?.changeAttributes)
fontPanel.setPanelFont(self.font, isMultiple: false)
fontPanel.orderBack(nil)
} label: {
Text("Font Selection: \(setSidebarFont)")
.font(.custom(setSidebarFont, size: CGFloat(setSidebarFontSize)))
}
}
}
func fontSelected() {
self.font = fontPanel.convert(self.font)
setSidebarFont = self.font.displayName ?? "System"
setSidebarFontSize = Int(self.font.pointSize)
var newAttributes = fontManager.convertAttributes([String : AnyObject]())
newAttributes["NSForegroundColorAttributeName"] = newAttributes["NSColor"]
newAttributes["NSUnderlineStyleAttributeName"] = newAttributes["NSUnderline"]
newAttributes["NSStrikethroughStyleAttributeName"] = newAttributes["NSStrikethrough"]
newAttributes["NSUnderlineColorAttributeName"] = newAttributes["NSUnderlineColor"]
newAttributes["NSStrikethroughColorAttributeName"] = newAttributes["NSStrikethroughColor"]
print("\(newAttributes["NSForegroundColorAttributeName"]!)")
}
}

How do you use the result of a function as a Bindable object in Swiftui?

I'm developing a simple SwiftUI app, using Xcode 11 beta5.
I have a list of Place, and i want to display the list, and add / edit them.
The data come from core data.
I have 3 classes for this :
- CoreDataController, which handle the connection to core data
- PlaceController, which handle operation on the Places.
public class CoreDataController {
static let instance = CoreDataController()
private let container = NSPersistentContainer(name: "RememberV2")
private init() {
print("Start Init DataController")
container.loadPersistentStores { (storeDescription, error) in
if let error = error {
fatalError("Failed to load store: \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
print("End Init DataController")
}
func getContext() -> NSManagedObjectContext {
return container.viewContext
}
func save() {
print("Start Save context")
do{
try container.viewContext.save()
} catch {
print("ERROR - saving context")
}
print("End Save context")
}
}
public class PlaceController {
static let instance = PlaceController()
private let dc = CoreDataController.instance
private let entityName:String = "Place"
private init() {
print("Start init Place Controller")
print("End init Place Controller")
}
func createPlace(name:String) -> Bool {
let newPlace = NSEntityDescription.insertNewObject(forEntityName: entityName, into: dc.getContext())
newPlace.setValue(UUID(), forKey: "id")
newPlace.setValue(name, forKey: "name")
dc.save()
DataController.instance.places = getAllPlaces()
return true
}
func createPlace(name:String, comment:String) -> Bool {
print("Start - create place with comment")
let newPlace = NSEntityDescription.insertNewObject(forEntityName: entityName, into: dc.getContext())
newPlace.setValue(UUID(), forKey: "id")
newPlace.setValue(name, forKey: "name")
newPlace.setValue(comment, forKey: "comment")
dc.save()
print("End - create place with comment")
DataController.instance.places = getAllPlaces()
return true
}
func getAllPlaces() -> [Place] {
let r = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
if let fp = try? dc.getContext().fetch(r) as? [Place] {
return fp
}
return [Place]()
}
func truncatePlaces() -> Bool {
let r = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
let batch = NSBatchDeleteRequest(fetchRequest: r)
if (try? dc.getContext().execute(batch)) != nil {
return true
}
return false
}
}
In my view i simply use the function :
List (pc.getAllPlaces(), id: \.id) { place in
NavigationLink(destination: PlaceDetail(place: place)) {
PlacesRow(place:place)
}
}
It works to display the information, but if i add a new place, the list is not updated.
I have to go back to the home screen, then display again the Places screen for the list to be updated.
So i use another controller :
class DataController: ObservableObject {
#Published var places:[Place] = []
static let instance = DataController()
private init() {
print("Start init Place Controller")
print("End init Place Controller")
}
}
In my view, i just display the ObservedObject places.
#ObservedObject var data: DataController = DataController.instance
And in my PlaceController, i update the table in the DataController
DataController.instance.places = getAllPlaces()
That works, but i have this warning :
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes
Also i'm pretty sure there is a better way to do this ...
Any idea what is this better way ?
Thanks,
Nicolas

How adapt code to work with a Tab Bar Controller

I'm redoing the navigation of my app to be based on a custom UITabBarController. The tab bar opens the various ViewController. This is working fine, however Im now getting errors with the code in the ViewController that was previously working.
The new customTabBarController
import UIKit
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let feedController = feedVC() //Name of the view controller
let firstNavigationController = UINavigationController(rootViewController: feedController)
firstNavigationController.title = "Feed"
firstNavigationController.tabBarItem.image = UIImage(named: "feed_icon")
let clubController = moreVC() //Name of the view controller
let secondNavigationController = UINavigationController(rootViewController: clubController)
secondNavigationController.title = "Club"
secondNavigationController.tabBarItem.image = UIImage(named: "club_icon")
let recordController = moreVC() //Name of the view controller
let thirdNavigationController = UINavigationController(rootViewController: recordController)
thirdNavigationController.title = "Record"
thirdNavigationController.tabBarItem.image = UIImage(named: "record_icon")
let profileController = moreVC() //Name of the view controller
let fourthNavigationController = UINavigationController(rootViewController: profileController)
fourthNavigationController.title = "Profile"
fourthNavigationController.tabBarItem.image = UIImage(named: "profile_icon")
let moreController = moreVC() //Name of the view controller
let fifthNavigationController = UINavigationController(rootViewController: moreController)
fifthNavigationController.title = "More"
fifthNavigationController.tabBarItem.image = UIImage(named: "more_icon")
viewControllers = [firstNavigationController, secondNavigationController, thirdNavigationController, fourthNavigationController, fifthNavigationController]
tabBar.isTranslucent = false
// Color of menu bar set in AppDelegate.swift
}
}
feedVC
import UIKit
import Firebase
class feedVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var activityArray = [Activity]()
var userdataArray = [Userdata]()
var cellSpacingHeight: CGFloat = 10 // Sets the spacing between the cells
override func viewDidLoad() {
super.viewDidLoad()
if Auth.auth().currentUser == nil {
let authVC = self.storyboard?.instantiateViewController(withIdentifier: "authVC") as? authVC
self.present(authVC!, animated: true, completion: nil)
}
tableView.delegate = self
tableView.dataSource = self
}
The error I get is:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102eb7b50) linked to the below code in the feedVC.
tableView.delegate = self
tableView.dataSource = self
The second error I get is related to recordVC
import UIKit
import MapKit
class recordVC: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var tileRenderer: MKTileOverlayRenderer!
// set initial location in Aspøya
let initialLocation = CLLocation(latitude: 63.011018, longitude: 7.914721)
// Set the zoom level of the location
let regionRadius: CLLocationDistance = 1000
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius, regionRadius)
mapView.setRegion(coordinateRegion, animated: true)
}
func setupTileRenderer() {
// Fetching the map file from URL below. The {x}, {y}, and {z} are replaced at runtime by an individual tile’s coordinate. The z-coordinate, or zoom-level is specified by how much the user has zoomed in the map. The x and y are the index of the tile given the section of the Earth shown. A tile needs to be supplied for every x and y for each zoom level supported.
let template = "https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=norgeskart_bakgrunn&zoom={z}&x={x}&y={y}&format=image/png"
// Creates the overlay
let overlay = MKTileOverlay(urlTemplate: template)
// Indicates the tiles are opaque and replace the default map tiles
overlay.canReplaceMapContent = true
// Adds the overlay to the mapView
mapView.add(overlay, level: .aboveLabels)
// Creates a tile renderer which handles the drawing of the tiles.
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
This give a similar error of: hread 1: EXC_BREAKPOINT (code=1, subcode=0x10561bb50) for this code line
// Adds the overlay to the mapView
mapView.add(overlay, level: .aboveLabels)
For the first one you have to use UITableViewDelegate and UITableViewDataSource like below :
class feedVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
....
}
For the second, You have to do UI operations on main thread.
DispatchQueue.main.async {
mapView.add(overlay, level: .aboveLabels)
}

transitioningDelegate never called after Segue transition

So I'm trying to implement a custom animation as my app transitions from one View Controller to another, but for some reason the animateTransition function in my custom animation class is never called.
For the record, I'm using Xcode 8 and writing in Swift 3. The problem I'm trying to over come, is that the function is never called - I'll sort out the actual animation in the future, for now its
Here is the code in my CustomPresentAnimationController class, which should handle the transition animation...
import UIKit
class CustomPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
let duration = 0.5
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
print("Checking duration")
return duration
}
func animationController(forPresented presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("This ran 1")
return self
}
func presentationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("This ran 2")
return self
}
func animationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("This ran 3")
return self
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
print("It's working!")
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {
return
}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
return
}
let container = transitionContext.containerView
let screenOffDown = CGAffineTransform(translationX: 0, y: -container.frame.height)
let screenOffUp = CGAffineTransform(translationX: 0, y: container.frame.height)
container.addSubview(fromView)
container.addSubview(toView)
toView.transform = screenOffUp
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: {
fromView.transform = screenOffDown
fromView.alpha = 0.5
toView.transform = CGAffineTransform.identity
toView.alpha = 1
}) { (success) in
transitionContext.completeTransition(success)
}
}
}
Here is the code for my ViewController (which both of my View Controllers reference)...
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate, UIViewControllerTransitioningDelegate {
override func viewDidLoad() {
if transitioningDelegate != nil {
print("Should do something...")
print(transitioningDelegate)
} else {
print("Transitioing Delegate set to nil")
}
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
let customPresentAnimationController = CustomPresentAnimationController()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("doing our custom transition")
print(segue.destination)
let destination = segue.destination
destination.transitioningDelegate = customPresentAnimationController
}
}
When I run the code, and click on the button I provided, which links to my seance View Controller, and is set to 'Present Modally', the view changes with the standard transition (slides up from the bottom) - and the following is printed out to Xcode:
Transitioing Delegate set to nil
doing our custom transition
<moduleView.ViewController: 0x7fe427f09a40>
Should do something...
Optional(<moduleView.CustomPresentAnimationController: 0x60800002e980>)
Obviously the first line is just as the first view loads, all the rest shows that my transitionDelegate is set on the Segue destination, and is indeed loaded in as the second view loads, and that the transitionDelegate is set to CustomPresentAnimationController... yet none of the functions in that class are ever called as it never prints anything out from those functions.
Any help appreciated!
Ensure the method signature for implementing the delegate matches the updated Swift 3 syntax.