Adding an UIImageView to the Subview - ios

I got following code:
func addObject(object: UIImageView)
{
view.addSubview(object)
}
and in another class I got this code:
func loadPlayground(width: CGFloat)
{
let playerX = screenWidth / fieldWidth * 2
let playerY = screenHight / fieldWidth * 2
let player1 = UIImageView(frame: CGRectMake(playerX, playerY, width, width))
player1.image = player
ViewController().addObject(player1)
print(playerX)
and the variable player is:
let player = UIImage(named: "Player.png")
Now my problem. My error code is:
Thread 1: EXC_BAD_ACCESS(code=2, address=0x114fbc).
It appears in the line where I'm trying to add the object to the Subview inside the class addObject. I also got the console output of the function various times. That let me conclude that the function got started often and stops after a time. Hopefully you can help me out.

In your code you are creating a new instance of ViewController everytime for adding the image view.
ViewController().addObject(player1)
So in the addObject method the view will be nil always, because it is not loaded yet.
For fixing this issue, you want to re-use the actual ViewController that is loaded currently or add the image view to the view after it is loaded.

Related

Detecting which ImageView has has been tapped

So i have 4 vStacks, each containing 9 ImageViews. Each ImageView represents one Card, alpha 0 is default. When a Card is Detected (with ARKit), my code sets the ImageView to alpha 1 so the user can see that the card has been scanned.
Now: I want to implement that when the user clicks on one of the ImageViews, an alert should pop up asking the user if he is sure he wants to delete the scanned card. My problem is, I have no idea what the best practice is to get the information that the card has been tapped and how to delete it without hardcoding.
In ViewDidLoad i set the images into the ImageVies like This:
//This repeats for all 36 ImageViews
imgView1.image = UIImage(named: "Eichel_6")
imgView2.image = UIImage(named: "Eichel_7")
/*When a image is detected with ARKit, this is what happens. Basically
*it pushes the corresponding reference name to an array called
* scannedCards, handles them, and removes them afterwards.
* spielPoints = gamepoints/points, spielmodus = gamemode
*/
func updateLabel() {
//print all cards in scanned cards
for card in scannedCards {
points += (DataController.shared.spielpoints[DataController.shared.spielmodus!]![card]! * DataController.shared.multCalculator[DataController.shared.spielmodus!]!)
}
scannedCards.removeAll()
}
I am a new to coding, I would be grateful if you correct me if my code snippets are bad, beside my question. Thank you in advance.
As has already been mentioned in comments, you should use a UICollectionView for this kind of work. #Fogmeister has promised to add an answer concerning that later, so I won't do that. But I can answer the actual question, even though it's not what you should do.
From your code I can see that you probably have outlets for all your imageViews (imgView1 ... imgView36) and set each image manually. To detect taps on any of these, you could do something like this:
func viewDidLoad(){
super.viewDidLoad()
let allImageViews = [imageView1, imageView2, .... imageView36]
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(gesture:)))
allImageViews.forEach({$0.addGestureRecognizer(tapGestureRecognizer)})
}
#objc func didTapImageView(gesture:UITapGestureRecognizer){
guard let imageView = gesture.view as? UIImageView else { return }
//Here you can put code that will happen regardless of which imageView was tapped.
imageView.alpha = 0.0
//If you need to know exactly which imageView was tapped, you can just check
if imageView == self.imageView1{
//Do stuff only for imageView1
}else if imageView == self.imageView2{
//...
}//....
}
Again, this is not very good practice. If you go for UICollectionView instead, you don't have to have outlets for all your imageViews and you don't have to create a gestureRecognizer for handling events. But still, I hope this helped you understand general gestures better.

Why does a function in my iOS app get called twice?

I made my very first iOS app. But there are two annoying bugs which I cannot get rid of. I hope somebody can help me!
The app is supposed to train to read musical notation. The user specifies his instrument and level (on the previous viewcontroller) and based on that, it places random notes in musical notation on the screen. The user should match those notes in textfields and the app keeps track of the score and advances a level after ten right answers.
However, somehow I'm having problems with the function which generates the random notes. The function for some reason gets called twice, the first time it generates the notes, saves them in a global variable and creates the labels with the notes. The second time, it changes the global variable but not the labels. It returns the following error message this time: 2018-09-29 23:08:37.279170+0200 MyProject[57733:4748212] Warning: Attempt to present <MyProject.ThirdViewController: 0x7fc709125890> on <MyProject.SecondViewController: 0x7fc70900fcd0> whose view is not in the window hierarchy!
Because of this, the user answers the question on the screen, but the app thinks it's the wrong answer, because it has the second answer stored.
The second time the user answers a question, the function is only called once, but the read-out from the text fields doesn't update to the new values, but keeps the same as with the first question.
Here is the code which gives the problems:
import UIKit
class ThirdViewController: UIViewController
{
// snip
func setupLabels() {
// snip
// here the random notes are created, this is function is called multiple times for some reason
let antwoord = Noten()
let antwoordReturn = antwoord.generateNoten(instrument: instrument, ijkpunt: ijkpunt, aantalNoten: aantalNoten-1)
let sleutel = antwoordReturn.0
let heleOpgave = antwoordReturn.1
print(heleOpgave)
print(PassOpgave.shared.category)
let heleOpgaveNummers = antwoordReturn.2
// snip
var a = 0
while a < aantalNoten {
// the labels are created, no problems there
let myTekstveld = UITextField(frame: CGRect(x: labelX, y: labelY + 150, width: labelWidth, height: labelHeight / 2))
myTekstveld.backgroundColor = UIColor.white
myTekstveld.textAlignment = .center
myTekstveld.placeholder = "?"
myTekstveld.keyboardType = UIKeyboardType.default
myTekstveld.borderStyle = UITextField.BorderStyle.line
myTekstveld.autocorrectionType = .no
myTekstveld.returnKeyType = UIReturnKeyType.done
myTekstveld.textColor = UIColor.init(displayP3Red: CGFloat(96.0/255.0), green: CGFloat(35.0/255.0), blue: CGFloat(123.0/255.0), alpha: 1)
myTekstveld.delegate = self as? UITextFieldDelegate
myTekstveld.tag = a + 1
view.addSubview(myTekstveld)
a += 1
labelX += labelWidth
}
// the button is created
}
override func viewDidLoad()
{
super.viewDidLoad()
// snip
setupLabels()
}
#objc func buttonAction(sender: UIButton!) {
// snip
// here the text from the text fields is read, but this only works the first time the buttonAction is called, the next times, it simply returns the first user input.
while a <= aantalNoten {
if let theLabel = view.viewWithTag(a) as? UITextField {
let tekstInput = theLabel.text!
userInput.append(tekstInput)
}
a += 1
}
// snip
setupLabels()
return
}
// snip
You have two instances of ThirdViewController when you don't mean to.
This error is very telling:
2018-09-29 23:08:37.279170+0200 MyProject[57733:4748212] Warning: Attempt to present <MyProject.ThirdViewController: 0x7fc709125890> on <MyProject.SecondViewController: 0x7fc70900fcd0> whose view is not in the window hierarchy!
This is telling you that SecondViewController is trying to create ThirdViewController when SecondViewController is not even on the screen. This suggests that the mistake is in SecondViewController (perhaps observing notifications or other behaviors when not on screen). It's possible of course that you also have two instances of SecondViewController.
I suspect you're trying to build all of this by hand rather than letting Storyboards do the work for you. That's fine, but these kinds of mistakes are a bit more common in that case. The best way to debug this further is to set some breakpoints and carefully check the address of the objects (0x7fc709125890 for example). You'll need to hunt down where you're creating an extra one.
Your genreteNoten method is being called multiple times because it is called from setupLabels which is In turn called from viewDidLoad.
viewDidLoad may be called multiple times and your code should account for that. As it says in this answer to a similar question:
If you have code that only needs to run once for your controller use -awakeFromNib.
I managed to partially solve my second problem myself (that the read-out from the text fields was not updating to the second answer) by not creating them again.
I added some code to setupLabels function to only create the text fields if there was no input already:
let myTekstveld = UITextField()
if (view.viewWithTag(a+1) as? UITextField) != nil {
}
else {
myTekstveld.frame = CGRect(x: labelX, y: labelY + 100, width: labelWidth, height: labelHeight / 2)
// snip
myTekstveld.tag = a + 1
view.addSubview(myTekstveld)
}
The app works as expected now, the only problem is that the text fields are not cleared after each question.

Swift 3 iOS 10 Reference to UIImageView to get Value for other function

I'm looking for a way to add codewise a way to reference to a UIImageView's value from another function.
I'm having a function which creates a new UIImageView for each instance in an array. Moreover, it also has an UITapGestureRecognizer added which calls a function when tapped. Now I want the function, which is called upon tap, to get the instance of that array which relates to the respective UIImageView.
I hope it's clear what I want to do. :)
Let's say you have an array of images:
let myImages = [UIImage(named: "image1"), UIImage(named: image2", UIImage(named: "image3"0]
And you have these images in three separate image views:
imageViewA.image = myImages[0]
imageViewB.image = myImages[1]
imageViewC.image = myImages[2]
Also, you've set up everything correctly that each image view calls the same function on a tap (we'll call it imageViewTapped()).
All you need to do is (1) properly set the tag property of each image view to point at the array index, (2) get the view that was tapped on, and (3) retrieve the image.
imageViewA.tag = 0
imageViewB.tag = 1
imageViewC.tag = 2
func imageViewTapped(_ recognizer:UITapGestureRecognizer) {
let tappedView = recognizer.view
let sourceImage = myImages[tappedView.tag]
}
I'd recommend setting everything up in a loop (tags, tap recognizer) and adding some type-casting in the function if needed. But I can't see your code to help you with that.

removeFromSuperview() view Not removed from first time

Ok I have this case where I insert 5 Views programmatically using this method:
let starView = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.width))
// Set Image & Alpha
starView.image = #imageLiteral(resourceName: "star")
starView.alpha = 1
starView.tag = starIndex
// Add to Super View
self.mainView.addSubview(starView)
Please note that starIndex for the 5 views are 1,2,3,4,5 consequently
It's Straightforward.
After a while when an event happens, I use another method to remove these views using this method:
func removeOldStars() {
for index in 1...5 {
if let foundView = view.viewWithTag(index) {
foundView.removeFromSuperview()
}
}
}
What happens here as a result is that the last element only "number 5" is removed. I have tried several trial and error and found this weird behavior. When I remove the view twice using the tag number it works. So for example, if I want to remove view with tag number 3 if I write
view.viewWithTag(3).removeFromSuperView()
view.viewWithTag(3).removeFromSuperView()
It works!!! if just one time it doesn't do anything. I thought maybe the view is added twice and so it need to be removed twice to notice it, but i Debugged it and no the view is added single time.
I removed the view in the main thread to be sure that its not threading issue not no its not the problem.
I would appreciate your help because this is so weird i really need to understand whats happening here.
Tags, in general, are a brittle way to reference views. As #Paulw11 mentioned, this is very likely an issue with other subviews having identical tag values.
In this case, I would hold on to instances of the UIImageViews, and then in the removeOldStars method, iterate through and call removeFromSuperview on the instance directly.
//instantiate empty array of UIImageView
var starViews = [UIImageView]()
//assuming your add method name..
func addStar() {
//your code above up to...
self.mainView.addSubview(starView)
starViews.append(starView)
}
func removeOldStars() {
for view in starViews {
view.removeFromSuperview()
//maybe explicitly de allocate the view depending
}
}

How do you call a function defined in another class?

I've created a SpriteKit project and defined a function in the GameScene.swift file. Said function creates a SKSpriteNode and has no problem running when called within it's class.
func generateShip() {
self.removeAllChildren()
let SKSprite1 = SKSpriteNode(imageNamed: "Spaceship")
SKSprite1.size = CGSize(width: CGFloat(arc4random_uniform(UInt32(200))), height: CGFloat(arc4random_uniform(UInt32(200))))
SKSprite1.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
self.addChild(SKSprite1)
}
//if I called it here with touchesBegan, it worked.
Now, in my ViewController, I created a simple button and connected it to its swift file. I did a few things before the following code, but this is the part I'm having trouble with.
let gameScene = GameScene()
#IBAction func newShip(sender: AnyObject) {
gameScene.generateShip() // am I doing this wrong?
}
When I run the app, and click the button, nothing happens. What do I need to change/ do to make function.... well ... function?
Links: Full Code , Iphone
The problem is in this code:
let gameScene = GameScene()
override func viewDidLoad() {
super.viewDidLoad()
let skView = self.view as! SKView
skView.presentScene(GameScene(fileNamed: "GameScene"))
}
Your gameScene property isn't the same as the scene you presented. Those are two separate objects. You need to do two things.
Change your gameScene declaration to be like the new one you created in your presenting code. Like so:
let gameScene = GameScene(fileNamed: "GameScene")
In viewDidLoad, present that GameScene object instead of making a new one, like this:
skView.presentScene(gameScene)
This way you create one GameScene object, present it, and call generateShip() on the same object.
In theory Swift should be able to link all the files automatically that are inside of your project, so your code should be able to work on its own. However, the only issue that might arise is because you are now relying on the StoryBoard as well, so you have to make sure thats linked correctly.
Make sure that its linked correctly.
Check out this question for more information on linking with Swift.
EDIT: Also, look at your code. You are calling the function for the space ship on the class itself and not on an instance of it, so thats why its probably not working. You need to first initialize a gameScene and then use that scene to call a method. I think it might be a lot more work so my advice would be to keep everything inside of your gameScene class and create a spriteNode button inside of there.
Your code seems fine to me.
If there was any 'access' problem with code from an unknown class / module etc. you'd get a compiler warning about it. This seems not to be the case.
My guess: your button is not wired up correctly!
Doublecheck that the #IBAction func newShip(sender: AnyObject) really gets called when you press the button: set a breakpoint inside of the newShip function.
Edit: And if it does stop there: step into the function using the debugger.
You are incorrectly using two different instances of GameScene in your view controller! One is added to the view hierarchy, and the OTHER instance is called when the button is tapped.
This can not be seen from the code in the original question, but from the code you provided in a comment: screenshot of full code
In your view controller your are first creating a GameScene with:
let gameScene = GameScene()
but then you are creating and adding a second instance of GameScene to the view hierarchy with:
skView.presentScene(GameScene(fileNamed:"GameScene"))
and your button action calls the generateShip() method on the first object stored in gameScene, that has not been presented by the SKView.
So the solution is to change the above code to:
let gameScene = GameScene(fileNamed:"GameScene"))
and later:
skView.presentScene(gameScene)

Resources