Swift NSTimer and IBOutlet Issue - swift

I'm using NSTimer to update an image's position on the screen to give the illusion of movement. It's a flappy bird clone I'm creating to learn Swift. Anyway, I'm also updating an outlet by displaying an incrementing Int as the score. However, every time I update the Int and the corresponding outlet, the images on the screen reset to their starting point (bird and tubes). Very odd. Has anyone else come across this? What am I doing wrong?
#IBOutlet weak var tube1: UIImageView!
#IBOutlet weak var bird1: UIImageView!
#IBOutlet weak var score: UILabel!
func startTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
bird1.center=CGPointMake(50,bird1.center.y+CGFloat(num));
tube1.center=CGPointMake(tube1.center.x-CGFloat(num2),tube1.center.y);
if(tube1.center.x<(-40)){
tube1.center.x=screenWidth;//variable set outside this method
tube1.center.y=0;//will be random
updateMyScore();
}
}
func updateMyScore(){
myScore=++;
score.text="Score: \(myScore)"
}
The weird thing is if I comment out the score.text="Score:(myScore)" line everything works fine, but I'd like to have the score show on the screen. Any suggestions?

AutoLayout is running and resetting the position of your images. It is running because you are updating your UILabel. There are 2 ways you can deal with this:
Disable AutoLayout OR
Store the positions of your tubes and pipes in properties and then set them in an override of viewDidLayoutSubviews().
Here is how you might implement option #2:
#IBOutlet weak var tube1: UIImageView!
#IBOutlet weak var bird1: UIImageView!
#IBOutlet weak var score: UILabel!
var tube1center: CGPoint?
var bird1center: CGPoint?
func startTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
bird1.center=CGPointMake(50,bird1.center.y+CGFloat(num));
tube1.center=CGPointMake(tube1.center.x-CGFloat(num2),tube1.center.y);
if(tube1.center.x<(-40)){
tube1.center.x=screenWidth;//variable set outside this method
tube1.center.y=0;//will be random
updateMyScore();
}
bird1center = bird1.center
tube1center = tube1.center
}
func updateMyScore(){
myScore=++;
score.text="Score: \(myScore)"
}
override func viewDidLayoutSubviews() {
if let newcenter = tube1center {
tube1.center = newcenter
}
if let newcenter = bird1center {
bird1.center = newcenter
}
}

Yes, this is well known behavior of auto layout. If you do anything that causes the auto layout engine to reapply the constraints to the view (such as updating the score), any adjustments to layout-related properties will be lost, reset to their values as dictated by the constraints.
As Vacawama pointed out, you could turn off auto layout. Alternatively, rather than manually changing the frame in your timer routine, you could instead create an IBOutlet for the constraints, themselves, and you can them modify the constraint's constant property and then call layoutIfNeeded. That way, if you happen to do something else that triggers the constraints, everything will be fine.

Related

Detect when mouse hovering over a view in a Mac app

I thought this would be simple.
I have a small single window Mac app.
I want to detect when the mouse is hovering over the mainview of my app.
I believe I need to use "onHover"
https://developer.apple.com/documentation/swiftui/text/onhover(perform:)#declaration
I found this answer SwiftUI Recognize mouse hover but it does't seem to work.
I declare the view:
#IBOutlet var mainView: NSView!
Then add the following code
mainView.onHover { hover in
print("Mouse hover: \(hover)")
}
But it errors with Value of type 'NSView' has no member 'onHover'
Any guidance would be fantastic.
EDIT: Im using the code given in the example on https://samoylov.eu/2016/09/05/mouseover-or-hover-effect-on-nsbutton-with-swift/
i'm declaring a button
#IBOutlet weak var startStopButton: NSButton!
then I added the code to track the rectangle
override func viewDidLoad() {
super.viewDidLoad()
// set tracking area
let area = NSTrackingArea.init(rect: startStopButton.bounds, options: [NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeAlways], owner: self, userInfo: nil)
startStopButton.addTrackingArea(area)
}
Then I added this code to the view
override func mouseEntered(theEvent: NSEvent) {
print("Entered")
}
override func mouseExited(theEvent: NSEvent) {
print("Exited")
}
But errors with "Declaration 'mouseEntered(theEvent:)' has different argument labels from any potential overrides"
Any idea where I went wrong?

UIView's animate method not appearing

I am trying to make an app that utilizes some search feature and I am trying to make it so that after the search button is pressed, a view (which contains the search results) moves up from the bottom of the superview and replaces the search view. On the storyboard, the resultsView (of type UIView) is constrained so that its top is equal to the superview's bottom. After the search button is pressed, I would like to animate the view to move up and replace the view already at the bottom of the superview. The problem is, in the viewcontroller's class, when I call the resultsView, the animateWithDuration(NSTimeInterval) that is supposed to be associated with the UIView class is not appearing for me. May this be because the view is already constrained in place? Here is the code, simplified for this post:
import UIKit
import MapKit
class MapViewController: UIViewController, CLLocationManagerDelegate,
MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var slider: UISlider!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var searchButton: UIButton!
#IBOutlet weak var searchView: UIView!
#IBOutlet weak var resultView: UIView!
#IBOutlet weak var resultNameLabel: UILabel!
#IBOutlet weak var resultDistanceLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
self.resultView.isHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func sliderAdjusted(_ sender: Any) {
let int = Int(slider.value)
switch int {
case 1:
distanceLabel.text = "1 mile"
default:
distanceLabel.text = "\(int) miles"
}
}
#IBAction func searchButtonPressed(_ sender: Any) {
/*This is where, after calling a search function which is omitted
from this example, I would like to make the resultsView not
hidden and then animate it to sort of eclipse the search view*/
self.resultView.isHidden = false
self.resultView.animate(withDuration: NSTimeInterval)
/*The above line of code is not actually appearing for me.
After typing "self.resultView." the animate function is not being
given to me as an option for my UIView*/
}
}
I will also attach some images of the view controller so you can sort of get the idea. The results view is not visible in this image because its top is constrained to the superview's bottom, thus it is just out of the visible representation of its superview.
The first image is the view controller with the searchView highlighted. This is the view that I would like to be eclipsed by my resultView after the searchButton is pressed.
The second image is the same view controller with the resultView highlighted. As you can see, its top is constrained to be equal to the superview's bottom. This is the view that I would like to animate upwards into the superview and eclipse the searchView after the search button is pressed.
The methods for all the animate family are all class methods. Which means you call them on the class object not an instance.
You are trying to call
class func animate(withDuration: TimeInterval, animations: () -> Void)
so your code needs to look like
UIView.animate(withDuration: 0.5) {
//the things you want to animate
//will animate with 0.5 seconds duration
}
In the particular case it looks like you are trying to animate the height of resultView so you need an IBOutlet to that constraint. You could call it resultViewHeight.
UIView.animate(withDuration: 0.5) {
self.resultViewHeight.constant = theDesiredHeight
self.layoutIfNeeded()
}
Calling layoutIfNeeded() within the closure is the secret sauce to animating auto layout. Without that the animation will just jump to the and point.

Error when I try to use NSTimer to change View Controller(EXC_BAD_INSTRUCTION) [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I am trying to change a view controller after an animation. I used an NSTimer to change the view controller after the animation is done. The animation takes place on the default ViewController given. After this animation runs once, it's supposed to use the prepareForSegue function and go to the second view controller. When I run it, the animation works, but it doesn't change view controllers.
import UIKit
class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
#IBOutlet weak var imageView: UIImageView!
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
var imagesNames = ["stairs", "stairs2", "stairs3", "stairs4", "stairs5", "stairs6", "stairs7", "stairs8", "stairs9", "stairs10", "stairs11", "stairs12", "stairs13", "stairs14", "stairs15", "stairs16", "stairs17", "stairs18", "stairs19", "stairs20", "stairs21", "stairs22", "stairs23", "stairs24", "stairs25", "stairs26", "stairs27", "stairs28", "stairs29", "stairs30", "stairs31", "stairs32", "stairs33", "stairs34", "stairs35", "stairs36", "stairs37", "stairs38", "stairs39", "stairs40", "stairs41", "stairs42", "stairs43", "stairs44", "stairs45", "stairs46","stairs47", "stairs48", "stairs49", "stairs50", "stairs51","stairs52", "stairs53", "stairs54", "stairs55", "stairs56","stairs57"]
var images = [UIImage]()
for i in 0..<imagesNames.count{
images.append(UIImage(named: imagesNames[i])!)
}
imageView.animationImages = images;
imageView.animationDuration = 4;
imageView.startAnimating()
_ = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: #selector(timeToMoveOn), userInfo: nil, repeats: false)
}
func timeToMoveOn() {
self.performSegueWithIdentifier("goToMainUI", sender: self)
}
There's a clue in the console output in your screenshot.
You're implicitly unwrapping imageView but it is nil. This probably means that there's no image view connected to that outlet.

(swift) update a number to label will make the view get back to original position

demo image
When I update a number to label will make the view get back to original position.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet var aview: UIView!
var number = 0
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func plus(sender: UIButton) {
number++
label.text = "number:\(number)"
}
#IBAction func move(sender: AnyObject) {
aview.frame.origin.y -= 20
}
}
I couldn't find the answer on web, please help me to fix this problem.Thank you very much!
Because your xib or Storyboard you set use Autolayout:
So if you don't set constrain system will auto generate it. When you change frame by set frame it effect but when you access to it. It will auto back to old position.
if you don't want it happen. You set in viewDidLoad:
self.view.translatesAutoresizingMaskIntoConstraints = false
Issue is probably related to a constraint reloading when the label reloads.
Instead of setting aview.frame.origin.y -= 20 you should make an outlet to the constraint holding the y position of your aView and then update the constant of that constraint outlet instead.

Swift - Using UIGestureRecognizer with UIImage

class ViewController: UIViewController {
#IBOutlet weak var grayView: UIView!
#IBOutlet weak var lightGrayView: UIView!
#IBOutlet weak var smileyFaceIMG: UIImageView!
#IBAction func smileyMove(sender: UIPanGestureRecognizer) {
var point = sender.locationInView(view)
smileyFaceIMG.center = point
if CGRectContainsPoint(lightGrayView.frame, smileyFaceIMG.center) {
smileyFaceIMG.image = UIImage(named: "Smiley_Face.jpg")
}
}
}
I have set up a UIImage which is supposed to change the image after I go over another UIView. This method seems to work with a regular UIView, however not with UIImage. How can I move the UIImage with the UIPanGestureRecognizer.
I learned what my mistake was. I needed to create a regular UIView, and then drag the UIImgView onto it. Then replace the code from the UIImageView to the UIView. Now everything works properly.