问题
I am trying to call a method from my view controller in my Scene.swift class - which I am able to do. The method is called after the touchesBegan method is called when I click on an SKLabel node, which are set up in my view controller.
The problem is that when I click on an SKLabelNode, control passes to the Scene.swift class and within the touchesBegan method, the method I want to call is called, so the control is passed back to the view controller, when I get back here it seems that all my variables are set to nil, as if its a completely different instance of the controller?
The error occurs in the checkIfValidTime method when I try to set the text property of a label in the ArViewController. - I highlighted these lines with **.
Error:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
How do I reference the same instance of the view controller so that the variables don't reset when I declare it in the Scene.Swift? Or is there a way I can implement the touchesBegan method in the view controller so that I don't have to instantiate the ARViewController?
I would appreciate any help on this matter as I have been stuck for a while now on it and I am new to iOS and swift app design.
I have tried to limit the code to what is necessary for me to explain this problem. Any questions just ask. Thanks
ARViewController:
public var receivedCallback : Bool = false
class ARViewController: UIViewController, ARSKViewDelegate, URLSessionDelegate {
// MARK: - IBOutlets
@IBOutlet weak var sceneView: ARSKView!
@IBOutlet weak var guideLabel: UILabel!
@IBOutlet weak var testLbl: UILabel!
var scene : Scene?
static var dateToUse : Date?
var aRLocalDate : Date?
var button: SKSpriteNode?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
/*
Start the view's AR session with a configuration that uses the rear camera,
device position and orientation tracking, and plane detection..
*/
let configuration = ARWorldTrackingConfiguration()
guard ARWorldTrackingConfiguration.isSupported else {
fatalError(""
ARKit is not available on this device."")
}
sceneView.session.run(configuration)
sceneView.delegate = self
if let scene = SKScene(fileNamed: "Scene"){
self.scene = scene as! Scene
sceneView.presentScene(self.scene)
} else {
print("Error: scene initalisation failed")
}
let overflow = ((aRLocalDate?.debugDescription.count)! - 11)
let endIndex = aRLocalDate?.debugDescription.index((aRLocalDate?.debugDescription.endIndex)!, offsetBy: -overflow)
if let truncatedDate = aRLocalDate?.debugDescription.substring(to: endIndex!){
DateLabel.text = truncatedDate
}
}
**/// - Tag: PlaceARContent**
func view(_ view : ARSKView, nodeFor anchor: ARAnchor) -> SKNode? {
if self.Generated == false{
self.guideLabel.alpha = 0
parentNode = SKShapeNode(rectOf: CGSize(width: 400, height: 720))
var count = 1;
for time in timesArray {
**//add a SKSpriteNode and assign image to it**
**let labelNode : SKLabelNode = SKLabelNode(text: time)**
labelNode.name = "booklabel" + String(count)
labelNode.isUserInteractionEnabled = false;
parentNode?.addChild(labelNode)
posy -= 60
count += 1
}
parentNode?.alpha = 0.6
self.Generated = true
drawEventNodes()
return parentNode
}
else {
return nil
}
}
//check if the booking is not in the past
func checkIfValidTime(bookingTime: String, startDateTimeDate: Date) -> Bool {
thisDate = ARViewController.dateToUse;
let date = Date()
let currentHour = Calendar.current.component(.hour, from: date)
if (startDateTimeDate > date) {
print("Start time is greater than the current date. = valid")
**self.guideLabel.text = "Test"**
return true;
}
else {
print("Start time is not valid - before current time")
**self.guideLabel.text = "Test"**
return false;
}
}
func doPost(bookingTime: String) {
print("Start of post method")
thisDate = ARViewController.dateToUse;
roomToBook = globalVariables.roomDictionary[globalVariables.userString]!
let name = globalVariables.userString;
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let displayName = globalVariables.userString
let startDateStr = dateFormatter.string(from: thisDate!)
let startHourString = bookingTime
print("StartDateStr:", startDateStr)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let startDateTimeString = "\(startDateStr)T\(startHourString)"
let startDateTimeDate = dateFormatter.date(from: startDateTimeString)
let endDateTimeDate = startDateTimeDate?.addingTimeInterval(3600)//3600 = 1 hour
let endDateTimeString = dateFormatter.string(from: endDateTimeDate!)
print("Start Date Time String", startDateTimeString)
print("End date time string", endDateTimeString)
print ("room to book: ",roomToBook)
let valid = checkIfValidTime(bookingTime: bookingTime, startDateTimeDate: startDateTimeDate!)
if (valid == true) {
let jsonObject: [String: Any] =
[
"subject": "Booking",
"body":[
"contentType": "HTML",
"content": "Test Booking"
],
"start":[
"dateTime": startDateTimeString,
"timeZone": "UTC"
],
"end": [
"dateTime": endDateTimeString,
"timeZone": "UTC"
],
"location":[
"displayName": displayName
],
"attendees":[[
"emailAddress": [
"address": roomToBook,
"name": displayName
],
"type": "required"
]]
]
//let valid = JSONSerialization.isValidJSONObject(jsonObject) // true
let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject)
// create post request
let url = URL(string: "https://graph.microsoft.com/v1.0/me/events")!
var request = URLRequest(url: url)
request.setValue("Bearer \(globalVariables.accessToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept") // the expected response is also JSON
request.httpMethod = "POST"
// insert json data to the request
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
}
task.resume()
print("Post Done")
print("Refreshing now")
//code to refresh?
}
else {
print("Invalid booking time - it is in the past.")
}
}
Scene.Swift:
class Scene : SKScene{
var controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ARStoryBoard") as! ARViewController
// var controller: ARViewController!
var bookingTime : String?
override func touchesBegan(_ touches: Set<UITouch>, with event : UIEvent?) {
// var c = self.view?.window?.rootViewController as! ARViewController;
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
let name = node.name
switch name {
case "booklabel1"?:
controller.doPost(bookingTime: "08:00:00")
case "booklabel2"?:
controller.doPost(bookingTime: "09:00:00")
case "booklabel3"?:
controller.doPost(bookingTime: "10:00:00")
case "booklabel4"?:
controller.doPost(bookingTime: "11:00:00")
case "booklabel5"?:
controller.doPost(bookingTime: "12:00:00")
case "booklabel6"?:
controller.doPost(bookingTime: "13:00:00")
case "booklabel7"?:
controller.doPost(bookingTime: "14:00:00")
case "booklabel8"?:
controller.doPost(bookingTime: "15:00:00")
case "booklabel9"?:
controller.doPost(bookingTime: "16:00:00")
case "booklabel10"?:
controller.doPost(bookingTime: "17:00:00")
case "booklabel11"?:
controller.doPost(bookingTime: "18:00:00")
default:
print ("No Specific Label Clicked")
}
}
}
}
回答1:
Try This.
ARViewController: Create instance outside the class
weak var arViewControllerInstance = ARViewController()
Make sure initialization inside ARViewController class:
override func viewDidLoad() {
super.viewDidLoad()
arViewControllerInstance = self
}
Now you can call in Scene.Swift using:
arViewControllerInstance?.doPost(bookingTime: "08:00:00")
回答2:
@Liam There is 'chain of ownership'. "Own" mean take responsibility for keeping object alive. Example Library, Reader and Book. Book had stamp of Library so it has reference to it, but definitely Book do not own Library. Same for reader. But both Library and Reader claims ownership of the book. If nobody take Book to Read declaring ownership) , and library is destroyed - them book is destroyed as well.
In Swift it is implemented via strong
and weak
references. All variables are strong
by default.
Usually you view controllers instantiated by app delegate, or by other view controllers (manually or using storyboard). Anyone else can be used by view controllers, and can be aware about viewcontroller but do not "own" it. So variable had to be declared as weak
. Mind that you don't own weak
, so it had to be optional and can be nullified at any moment (if user navigate back), so you had to use additional guard.
To summarise:
1: Declare your viewController in the scene as weak:
class Scene : SKScene{
weak var controller: ARViewController?
....*
}
2: Supply your scene with view controller. You can do it when creating Scene, or when setting it. For example:
class ARViewController: UIViewController, ARSKViewDelegate, URLSessionDelegate {
//...
var scene : Scene? {
didSet{
//Optional: In case you can change scenes - remove view controller from old scene
oldValue?.controller = nil
//Actually set view controller of any scene it "own"
scene?.controller = self
}
}
PS: there is additional modifier "unowned". But it is more advance technique, can cause issues and crashes. I advice you to make yourself familiar with weak
. Get used to guard
retain cycles and then proceed.
来源:https://stackoverflow.com/questions/48927609/referring-to-my-view-controller-in-another-class-scene-swift-all-variables-t