Good day. I'm creating my first own app and have ran into an issue. I have a AR scene with clickable stuff which when you touch them a segue triggers into a ViewController and sets that view controllers labels and textview depending on what was touch on screen.
Explanations: 1. CaseViewController is the target view controller. 2. the "artNews" and "politicalNews" are string arrays in which I've written 3 strings, they are defined and are never nil.
Question: I get a crash due to the segueInputText being nil. Why does it become nil and how do I correct it?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! CaseViewController
destinationVC.segueInputText = sender as? [String]
print("\(String(describing: sender))")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchLocation = touches.first?.location(in: sceneView),
let hitNode = sceneView?.hitTest(touchLocation, options: nil).first?.node,
let nodeName = hitNode.name
else { return }
if nodeName == imageNameArray[0] {
performSegue(withIdentifier: "CaseViewController", sender: artNews)
} else {
print("Found no node connected to \(nodeName)")
return
}
if nodeName == imageNameArray[1] {
performSegue(withIdentifier: "CaseViewController", sender: politicalNews)
} else {
print("Found no node connected to \(nodeName)")
return
}
the CaseViewController has UILabels and UITextViews connected and this:
var segueInputText : [String]? {
didSet {
setupText()
}
}
func setupText() {
// Why are these values nil?
testLabel.text = segueInputText![0]
ingressLabel.text = segueInputText![1]
breadLabel.text = segueInputText![2]
testLabel.reloadInputViews()
ingressLabel.reloadInputViews()
breadLabel.reloadInputViews() //breadLabel is a UITextView
}
Thank you for reading my question!
Kind regards.
Remove didSet block as when you set the array inside prepare , observe triggers and the lbl is still nil
OR
func setupText() {
if testLabel == nil { // if one is nil then all of them too
return
}
}
Don't use the didSet observer in this case. It will never work.
In setupText() IBOutlets are accessed which are not connected yet at the moment prepare(for is called.
Remove the observer
var segueInputText : [String]?
and call setupText in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupText()
}
At the very moment when you do this:
destinationVC.segueInputText = sender as? [String]
the destination view controller has not yet loaded hence none of the outlets are connected so accessing any of them will crash your app as they are still nil.
You will have to assign any of the values you’d like to pass to the destination controller to a property and assign this property’s value to the the corresponding outlet in viewDidLoad. This way you make sure all outlets have connected.
For the same reason don’t use a property observer to assign the property’s value to any of the labels as this would happen, again, before the view controller had a chance to load…
Related
ScrollView is not recognising shake gesture
I have 3 files.
1.ContentVC - consists of scrollView to swipe between viewcontrollers
2.FirstVC - It contains shakegesture function
3.SecondVC - default view controller
When i shake the device nothing happens.
ContentVC.swift
class ContainerVC: UIViewController {
#IBOutlet weak var scroll: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let left = self.storyboard?.instantiateViewController(withIdentifier: "vw") as! UINavigationController
self.addChild(left)
self.scroll.addSubview(left.view)
self.didMove(toParent: self)
let last = self.storyboard?.instantiateViewController(withIdentifier: "lastviewNav") as! UINavigationController
self.addChild(last)
self.scroll.addSubview(last.view)
self.didMove(toParent: self)
var middleframe:CGRect = last.view.frame
middleframe.origin.x = self.view.frame.width
last.view.frame = middleframe
self.scroll.contentSize = CGSize(width: (self.view.frame.width) * 2, height: (self.view.frame.height))
}
}
FirstVC.swift
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if event?.subtype == UIEvent.EventSubtype.motionShake {
if motion == .motionShake {
print("quote is appearing by shaking the device")
} else {
print("No quote is coming")
}
}
}
Motion events are delivered initially to the first responder and are forwarded up the responder chain as appropriate.
https://developer.apple.com/documentation/uikit/uiresponder/1621090-motionended
So make sure that:
FirstVC able to became first responder:
override var canBecomeFirstResponder: Bool {
return true
}
FirstVC is first responder at specific point of time:
firstVC.becomeFirstResponder()
Now your UIViewController will be able to receive shake motion events.
You can read more about Responder chain: How do Responder Chain Works in IPhone? What are the "next responders"?
I am sending data from one tableview list to another tableview used as a detail view for the selected cell. Simple -
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
let index = tableView.indexPathForSelectedRow
if (segue.identifier == "EventDetailSegue") {
let detailVC = segue.destinationViewController as! EventDetailTableViewController
var event : Event
if searchController.active && searchController.searchBar.text != "" {
event = filteredEvents[index!.row]
} else {
event = events[index!.row]
}
detailVC.eventImage = event.image
detailVC.eventCompany = event.company
detailVC.eventName = event.name
detailVC.eventLocation = event.location
detailVC.eventPrice = event.price
detailVC.eventPromoterImage = event.promoterImage
}
}
Since I now want to have a static button bar on the bottom of the detail tableview. I need to change the EventDetailTableViewController to be in a container view on a UIViewController. So I can then simply do what I want inside the UIViewController.
My question is how do I still send this data to the now embedded EventDetailTableViewController inside the container view? Since the segue will now need to be going to the UIViewController with the container view, but I also need to make sure this data is passed to that TableView inside the container.
i use color instead of data :
EventDetailTableViewController ->ViewController3
UIViewController -> ViewController2
try like this :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let tableVc = ViewController3()
tableVc.color = UIColor.redColor()
let vc = ViewController2()
vc.tableViewController = tableVc
self.navigationController?.pushViewController(vc, animated: true)
}
}
class ViewController2: UIViewController {
var tableViewController:ViewController3!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.yellowColor()
tableViewController.willMoveToParentViewController(self)
addChildViewController(tableViewController)
tableViewController.view.frame = CGRect(x: 0, y: 100, width: 100, height: 100)
view.addSubview(tableViewController.view)
tableViewController.didMoveToParentViewController(self)
}
}
class ViewController3: UITableViewController {
var color :UIColor!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = color
}
}
hope it be helpful :-)
I have two UILabels with two UITapGestureRecognizers in a UITableViewCell.
cell.Username.tag = indexPath.row
cell.SharedUser.tag = indexPath.row
let tapGestureRecognizer2 = UITapGestureRecognizer(target:self, action:"GoToProfil:")
let tapGestureRecognizer3 = UITapGestureRecognizer(target:self, action:"GoToProfil:")
cell.Username.userInteractionEnabled = true
cell.Username.addGestureRecognizer(tapGestureRecognizer2)
cell.SharedUser.userInteractionEnabled = true
cell.SharedUser.addGestureRecognizer(tapGestureRecognizer3)
func GoToProfil (sender: AnyObject!) {
self.performSegueWithIdentifier("GoToProfilSegue", sender: sender)
}
I'm using a Segue to push another UIViewController, and I'm overriding the PrepareSegue function to send the needed information corresponding to the Sender tag.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let ProfilView = segue.destinationViewController as! Profil
ProfilView.hidesBottomBarWhenPushed = true
ProfilView.title = posts[sender.view!.tag].User?.objectForKey("Name") as? String
ProfilView.User = posts[sender.view!.tag].User
}
My problem is that I want to know which UILabel was pressed, knowing that I'm already using tag.
Your GoToProfile: function should be written properly. The parameter isn't the "sender", it's the gesture recognizer.
func GoToProfil (gestureRecognizer: UITapGestureRecognizer) {
}
From there, you can determine the label by using the view property of the gesture recognizer.
But you seem to have two conflicting requirements. You want to know which of the two labels was tapped and you want to know which row the label is in.
Normally you would use the label's tag to know which of the two labels was tapped. But you are using their tags to track the row.
The solution I recommend is to use the tag to differentiate the two labels. Then you can calculate the row based on the frame of the label.
See the following answer for sample code that translates the frame of a cell's subview to the cell's indexPath.
Making the following assumptions:
You are trying to uniquely identify the label using UIView.tag
You want different behaviour for Username & SharedUser
I recommend the following, first define your tags below your #imports
#define kUsername 1
#define kSharedUser 2
Then assign them to your views
cell.Username.tag = kUsername
cell.SharedUser.tag = kSharedUser
Then in your prepareSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
int tag = [sender.view!.tag]
if (tag == kUsername) {
//Username logic
} else if(tag == kSharedUser) {
//Shared User Logic
}
}
This way you can easily and simply determine tap, Note this might have different results if you have more then 1 Username & SharedUser labels. Then you will either need more #defines or change how you generate your tags.
You can add a property to UILabel to track the label's type. (I used an enum since there's just 2 cases, but it could be a string, etc.)
enum LabelDest : String
{
case Username = "Username"
case SharedUser = "SharedUser"
}
extension UILabel
{
struct Static {
static var key = "labelDest"
}
var labelDest:LabelDest? {
set { objc_setAssociatedObject( self, &Static.key, newValue?.rawValue, .OBJC_ASSOCIATION_COPY_NONATOMIC )
}
get {
guard let val = objc_getAssociatedObject( self, &Static.key ) as? String else { return nil }
return LabelDest( rawValue:val )
}
}
}
Now you can just do this:
let label = UILabel()
label.labelDest = .Username
Later:
switch label.labelDest
{
case .Some(.Username):
// handle user name
break
...
If you want to use the .tag field on your labels you can use a different technique to find the table row associated with a label: (again using class extensions)
extension UIView
{
var enclosingTableViewCell:UITableViewCell? {
return superview?.enclosingTableViewCell
}
var enclosingTableView:UITableView? {
return superview?.enclosingTableView
}
}
extension UITableViewCell
{
var enclosingTableViewCell:UITableViewCell? {
return self
}
}
extension UITableView
{
var enclosingTableView:UITableView? {
return self
}
}
extension UIView {
var tableRow:Int? {
guard let cell = self.enclosingTableViewCell else { return nil }
return self.enclosingTableView?.indexPathForCell( cell )?.row
}
}
Now, from your gesture recognizer action:
func goToProfil( sender:UIGestureRecognizer! )
{
guard let tappedRow = sender.view?.tableRow else { return }
// handle tap here...
}
You can access the sender data, and read the tag of the object that send you, like in this sample code.
To uniquely identify each row and each label, you can use something like this:
cell.Username.tag = (indexPath.row*2)
cell.SharedUser.tag = (indexPath.row*2)+1
With this, if you have a even tag, its the Username, odd will be the SharedUser. Dividing by the floor of 2 you can have the row back.
#IBOutlet weak var test1: UILabel!
#IBOutlet weak var test2: UILabel!
override func viewWillAppear(animated: Bool) {
test1.tag = 1
test2.tag = 2
test1.userInteractionEnabled = true
test2.userInteractionEnabled = true
self.test1.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleSingleTap:"))
self.test2.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleSingleTap:"))
}
func handleSingleTap(sender: UITapGestureRecognizer) {
print(sender.view?.tag)
}
I'm trying to change a method in ViewDidLoad with this code:
In class declaration:
var nextValue: Int!
And in ViewDidLoad:
if nextValue == nil {
print("Hi")
} else if nextValue == 2 {
print("Hello")
}
And finally this function that changes the value of nextValue:
func buttonAction(sender: AnyObject) {
self.performSegueWithIdentifier("nextView", sender: self)
nextValue = 2
}
When I move back from "nextView" to the first view nextValue should be 2 but it is nil. What am I doing wrong?
Your understanding about view life cycle is wrong.
First, you declare your variable with nil value in class declaration.
then, during viewDidLoad method you check its value, finally you change its value with some button action.
However, when you leave your view controller screen via segue to nextView, your firstView gets deallocated and when you represent it again, the cycle goes back to declaration level. since you declare your variable value as nil, it will always show nil value.
if you want to retain its value, you need to save it somewhere else, NSUserDefault seems like a good choice to store its value.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
nextValue = NSUserDefaults.standardUserDefaults().valueForKey("nextValue") as? Int
if nextValue == nil {
print("Hi")
} else if nextValue == 2 {
print("Hello")
}
}
func buttonAction(sender: AnyObject) {
self.performSegueWithIdentifier("nextView", sender: self)
nextValue = 2
NSUserDefaults.standardUserDefaults().setInteger(2, forKey: "nextValue")
}
i tried to add gesture recognizer in my UIImageView
let rc = UITapGestureRecognizer(target: self, action: "foo:")
rc.numberOfTapsRequired = 1
rc.numberOfTouchesRequired = 1
cell.bar.tag = indexPath.row
cell.bar.addGestureRecognizer(rc)
but didn't call my foo function
func foo(sender: UIImageView! ) {
self.performSegueWithIdentifier("VC", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "VC" {
let vc = segue.destinationViewController as! VC
vc.item = items[sender.tag]
}
}
So, as I mentioned before your problem is UIImageView by default has property userInteractionEnabled set to false. You can change this in you storyboard, or add line cell.bar.userInteractionEnabled = true.
Next your problem is in your foo: method implementation: you specify sender as UIImageView!, but it should be UITapGestureRecognizer. This is why it crashes - it cannot be UIImageView, so when it unwraps (!) it is nil.
Solution: change your foo method declaration to foo(recognizer: UITapGestureRecognizer). If you need access your imageView inside this method you can use code below:
if let imageView = recognizer.view as? UIImageView {
...
}
or with new guard keyword (Swift 2.0)
guard let imageView = recognizer.view as? UIImageView
else { return }
...
You can change
foo(recognizer: UITapGestureRecognizer) {
}