So I created my first label without story board today and I am wondering how to change the text of the label when I click a button. The action for the button works just fine. I did create an outlet for the label so I could refer to it out of the method I initially called it in but when I click the button the app crashes.
Here is my code:
import UIKit
import Foundation
class ThirdViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
//label one
var label1 = UILabel(frame: CGRectMake(0, 0, view.bounds.width - 80, 30))
label1.center = CGPointMake(view.bounds.width/2, 20)
label1.textAlignment = NSTextAlignment.Center
label1.text = "Pick the largest number"
label1.font = UIFont(name: font, size: 25)
self.view.addSubview(label1)
//number one
var num1 = UIButton(frame: CGRectMake(0, 0, 100, 100))
num1.center = CGPointMake(view.bounds.width/4, view.bounds.height/4)
num1.setTitle("37", forState: UIControlState.Normal)
num1.addTarget(self, action: "num1Tapped", forControlEvents: UIControlEvents.TouchUpInside)
num1.titleLabel!.font = UIFont(name: font, size: 70)
self.view.addSubview(num1)
}
func num1Tapped(UIButton!){
label1.text = "test"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
There are a few problems with your code.
The property you define on top of class definition #IBOutlet weak var label1: UILabel! is not the same you define in (inside) the method viewDidLoad().
Here, you are creating a new local variable, with the same name of your property, an instance variable!
And this variable you created is only accessible inside the method, that's the scope for which it is defined.
The method num1Tapped(_:) is, as it has already been said here, incorrectly defined.
The action that is triggered by an UIControl can have three different signatures:
action()
action(_:)
action(_: forEvent:)
If you don't need a reference of the button and/or the event that triggered the action, you can just use the first one.
As it is now, and forgetting the wrong definition, when the method num1Tapped(_:) is called, it will try to access the instance variable label1, not the local variable defined in the method viewDidLoad(), which, by the way, is impossible to do it.
So, the next thing to resolve is this instance variable label1.
As you have defined, you must connect this declaration with an Outlet in the storyboard, or you must manually initialize the variable.
This is the code from your example that I used (with some modifications) to change the label when the button is touched, and assuming you are creating the label with code:
class ThirdViewController: UIViewController {
var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//label one
label1 = UILabel(frame: CGRectMake(0, 0, view.bounds.width - 80, 30))
label1.center = CGPointMake(view.bounds.width/2, 20)
label1.textAlignment = NSTextAlignment.Center
label1.text = "Pick the largest number"
// label1.font = UIFont(name: font, size: 25)
view.addSubview(label1)
//number one
// let num1 = UIButton(frame: CGRectMake(0, 0, 100, 100))
let num1 = UIButton(type: .System)
num1.frame = CGRectMake(0, 0, 100, 100)
print("num1: \(num1)")
num1.center = CGPointMake(self.view.bounds.width/4, self.view.bounds.height/4)
num1.setTitle("37", forState: UIControlState.Normal)
num1.addTarget(self, action: "num1Tapped", forControlEvents: UIControlEvents.TouchUpInside)
// num1.addTarget(self, action: "num1TappedWithParameter:", forControlEvents: UIControlEvents.TouchUpInside)
// num1.addTarget(self, action: "num1TappedWithTwoParameters:event:", forControlEvents: UIControlEvents.TouchUpInside)
// num1.titleLabel!.font = UIFont(name: font, size: 70)
self.view.addSubview(num1)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func num1Tapped() {
label1.text = "test"
}
func num1TappedWithParameter(sender: UIButton){
label1.text = "test"
}
func num1TappedWithTwoParameters(sender: UIButton, event: UIEvent) {
print("button: \(sender.description) event: \(event)")
}
}
A few extra notes.
With the code you present I don't understand how it even compiles with the wrong signature of the method num1Tapped(UIButton!). This doesn't compile!
Did you change it (with the Xcode suggestion) to num1Tapped(_ :UIButton!)?
If you did, the app crashes because it can find the method you are calling since you defined as it having no parameters:
num1.addTarget(self, action: "num1Tapped", forControlEvents: UIControlEvents.TouchUpInside)
Another thing I don't understand is how can your button being displayed because you it's no set its type.
Did you define the button num1in storyboard and you (wrongly) tried to access here in the code?
addBackground()is commented since it's not here defined.
The same happens when setting the font.
I add two more methods so you can see how to use them.
Be consistent when writing your code. If you access the variable view without the self, do the same when adding the subviews: view.addSubview(num1), or of course, the opposite, use self in both situations.
I hope this helps with your problem.
Your code is almost correct except of a few small changes:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
//label one
var label1 = UILabel(frame: CGRectMake(0, 0, view.bounds.width - 80, 30))
label1.center = CGPointMake(view.bounds.width/2, 20)
label1.textAlignment = NSTextAlignment.Center
label1.text = "Pick the largest number"
label1.font = UIFont(name: font, size: 25)
self.view.addSubview(label1)
//number one
var num1 = UIButton(frame: CGRectMake(0, 0, 100, 100))
num1.center = CGPointMake(view.bounds.width/4, view.bounds.height/4)
num1.setTitle("37", forState: UIControlState.Normal)
num1.addTarget(self, action: "num1Tapped:", forControlEvents: UIControlEvents.TouchUpInside)
num1.titleLabel!.font = UIFont(name: font, size: 70)
self.view.addSubview(num1)
}
func num1Tapped(sender: UIButton!){
label1.text = "test"
}
#user2893289 was correct and so was #Arc676. You need to add the ":" in the action and add sender: UIButton! as the buttons action.
Related
I am trying to make a Draggable label programmatically, I can get the Label to appear but as soon as I try to drag it I throw the error. Any suggestions? There is absolutely nothing else on the screen. it is an empty View controller to start with.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let label = UILabel(frame: CGRectMake(self.view.bounds.width / 2 - 100 , self.view.bounds.height / 2 - 50, 200, 100))
label.text = "Drag Me!"
label.textAlignment = NSTextAlignment.Center
self.view.addSubview(label)
let gesture = UIPanGestureRecognizer(target: self, action: Selector("Was Dragged:"))
label.addGestureRecognizer(gesture)
label.userInteractionEnabled = true
}
You should not use a space in the selector name. And did you create the function with the name of the selector ?
Try this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let label = UILabel(frame: CGRectMake(self.view.bounds.width / 2 - 100 , self.view.bounds.height / 2 - 50, 200, 100))
label.text = "Drag Me!"
label.textAlignment = NSTextAlignment.Center
self.view.addSubview(label)
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged"))
label.addGestureRecognizer(gesture)
label.userInteractionEnabled = true
}
func wasDragged() {
print("i've been dragged")
}
I'm still learning how the view works, but I found a problem that I couldn't solve...
I got the class GraficsBalancGlobalViewControllerwhich is subclass of the class GraficViewController
class GraficsBalancGlobalViewController: GraficViewController {
#IBAction func afegeixGrafic(sender: NSButton) {
addNewGrafic() // which is set on the GraficViewController
}
}
And when I perform the IBAction afegeixGrafic my program crashes on the line marked below:
class GraficViewController: NSViewController, GraficViewDataSource {
#IBAction func button(sender: NSButton) {
addNewGrafic()
}
func addNewGrafic() {
let frame = NSRect(x: 0, y: 0, width: self.view.bounds.width , height: self.view.bounds.width * 0.25)
let nouGrafic = GraficView(frame: frame)
scrollView.addSubview(nouGrafic) <---- BREAK here!
}
#IBOutlet weak var scrollView: NSView!
//...more code
}
The compiler says that:
fatal error: unexpectedly found nil while unwrapping an Optional value
but the button (IBAction) inside the GraficViewController works well!! So i suppose that the problem is related with the scrollView, but I have no idea of what can be.. It is initialized..
just to mention that the GraficView(frame: frame)is not the problem because I try it and works well.
I do believe ! is the culprit of your woes:
#IBOutlet weak var scrollView: NSView!
Xcode does generate this entry with force unwrap (!) for IBOutlets, but it should really be an optional (?) because you have no guaranties when that reference is going to be set. If you have some logic that depend on scrollView's existence, you can do so by relying on didSet:
#IBOutlet weak var scrollView: NSView? {
didSet {
guard let sview = scrollView else {
return // because scrollView is nil for some reason
}
// do your scrollView existence dependent logic here (eg. reload content)
}
}
func addNewGrafic() {
let frame = NSRect(x: 0, y: 0, width: self.view.bounds.width , height: self.view.bounds.width * 0.25)
let nouGrafic = GraficView(frame: frame)
scrollView?.addSubview(nouGrafic)
}
I hope this helps.
Hope someone can help me. Im trying to make a zoom gesture, so when the image are presented the user can zoom the image with fingers.
My code to present the image are:
// MARK: Show image full screen
func imageTapped(img: AnyObject) {
self.navigationController?.navigationBarHidden = true
let imageView = productImage as UIImageView
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = self.view.frame
newImageView.backgroundColor = .blackColor()
newImageView.contentMode = .ScaleToFill
newImageView.userInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: "dismissFullscreenImage:")
newImageView.addGestureRecognizer(tap)
self.view.addSubview(newImageView)
}
func dismissFullscreenImage(sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
self.navigationController?.navigationBarHidden = false
}
Use UIScrollView and add UIImgeView in scroll view
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate
{
var scrollV : UIScrollView!
var imageView : UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationController?.navigationBarHidden = true
scrollV=UIScrollView()
scrollV.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
scrollV.minimumZoomScale=1
scrollV.maximumZoomScale=3
scrollV.bounces=false
scrollV.delegate=self;
self.view.addSubview(scrollV)
imageView=UIImageView()
imageView.image = UIImage(imageLiteral: "neymar.jpg")
imageView.frame = CGRectMake(0, 0, scrollV.frame.width, scrollV.frame.height)
imageView.backgroundColor = .blackColor()
imageView.contentMode = .ScaleToFill
scrollV.addSubview(imageView)
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
{
return imageView
}
}
Take gesture programmatically and make its method and write down this code in the method.
pinchRecognizerOnView() is my method name. Take one view or image view inside view controller and add this new view in that.Now apply gesture method on this new view.
func pinchRecognizerOnView(sender: UIPinchGestureRecognizer!) {
sender.view?.transform = CGAffineTransformScale((sender.view?.transform)!, sender.scale, sender.scale)
sender.scale = 1
// its for zoom in out screen
}
I want to implement a feature that when an user hovers over the specific area, the new view appears with drawer-like animation. And also, when the user leaves the specific area, the drawer should go away with animation. This is exactly what you see when you hover over the bottom of the screen in OS X, where the Dock appears and disappears with animation.
However, if I implement the feature with animation, it does not work properly when you re-enter the specific area before the animation in the mouseExited: is completed. Here's my code:
let trackingArea = NSTrackingArea(rect: CGRectMake(0, 0, 120, 300), options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.MouseEnteredAndExited, owner: self, userInfo: nil)
underView.addTrackingArea(trackingArea) // underView is the dummy view just to respond to the mouse tracking, since the drawerView's frame is changed during the animation; not sure if this is the clean way...
override func mouseEntered(theEvent: NSEvent) {
let frameAfterVisible = CGRectMake(0, 0, 120, 300)
NSAnimationContext.runAnimationGroup({
(context: NSAnimationContext!) in
context.duration = 0.6
self.drawerView.animator().frame = frameAfterVisible
}, completionHandler: { () -> Void in
})
}
override func mouseExited(theEvent: NSEvent) {
let frameAfterInvisible = CGRectMake(-120, 0, 120, 300)
NSAnimationContext.runAnimationGroup({
(context: NSAnimationContext!) in
context.duration = 0.6
self.drawerView.animator().frame = frameAfterInvisible
}, completionHandler: { () -> Void in
})
}
// drawerView's frame upon launch is (-120, 0, 120, 300), since it is not visible at first
In this code, I animate the drawerView by altering its x position. However, as I stated, when you enter the tracking area and then leave the tracking area, the drawer works correctly. But that is not the case if you re-enter the tracking area before the leave-off animation is fully completed.
Of course if I set the animation duration shorter, such as 0.1, this would rarely occur. But I want to move the view with animation.
What I want to do is make the drawerView start to appear again even if the view has not completed disappearing. Is there any practice to do it?
I have a solution that is very similar to your code. What I do different is is that I install the NSTrackingArea not on the view that contains the drawer view, but on the drawer view itself.
This obviously means that the drawer needs to 'stick out' a little bit. In my case the drawer is a small bit visible when it is down because I put an image view in it. If you don't want that then I suggest you just leave the visible area of the drawer empty and translucent.
Here is my implementation:
private enum DrawerPosition {
case Up, Down
}
private let DrawerHeightWhenDown: CGFloat = 16
private let DrawerAnimationDuration: NSTimeInterval = 0.75
class ViewController: NSViewController {
#IBOutlet weak var drawerView: NSImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Remove the auto-constraints for the image view otherwise we are not able to change its position
view.removeConstraints(view.constraints)
drawerView.frame = frameForDrawerAtPosition(.Down)
let trackingArea = NSTrackingArea(rect: drawerView.bounds,
options: NSTrackingAreaOptions.ActiveInKeyWindow|NSTrackingAreaOptions.MouseEnteredAndExited,
owner: self, userInfo: nil)
drawerView.addTrackingArea(trackingArea)
}
private func frameForDrawerAtPosition(position: DrawerPosition) -> NSRect {
var frame = drawerView.frame
switch position {
case .Up:
frame.origin.y = 0
break
case .Down:
frame.origin.y = (-frame.size.height) + DrawerHeightWhenDown
break
}
return frame
}
override func mouseEntered(event: NSEvent) {
NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext!) in
context.duration = DrawerAnimationDuration
self.drawerView.animator().frame = self.frameForDrawerAtPosition(.Up)
}, completionHandler: nil)
}
override func mouseExited(theEvent: NSEvent) {
NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext!) in
context.duration = DrawerAnimationDuration
self.drawerView.animator().frame = self.frameForDrawerAtPosition(.Down)
}, completionHandler: nil)
}
}
Full project at https://github.com/st3fan/StackOverflow-28777670-TrackingArea
Let me know if this was useful. Happy to make changes.
Starting Swift 3. You need to do it like this:
let trackingArea = NSTrackingArea(rect: CGRectMake(0, 0, 120, 300), options: [NSTrackingAreaOptions.ActiveAlways ,NSTrackingAreaOptions.MouseEnteredAndExited], owner: self, userInfo: nil)
view.addTrackingArea(trackingArea)
Credits #marc above!
(I just started using Swift a few days ago and am relatively new to programming, so please bear with me.) I am trying to make random blocks appear on the screen, and the user must tap them to make them disappear. I have been able to create the blocks, but I have no idea how to actually make them tappable. Can someone please help me? This is my code so far:
func createBlock(){
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -50, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
})
}
Edit: This is the new code I am trying:
func createBlock(){
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -50, width: size, height: size)
self.view.addSubview(imageView)
imageView.userInteractionEnabled = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped"))
imageView.addGestureRecognizer(tapRecognizer)
func imageTapped(gestureRecognizer: UITapGestureRecognizer) {
let tappedImageView = gestureRecognizer.view!
tappedImageView.removeFromSuperview()
score += 10
}
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
})
}
Once you've create the view, you need to set it's userInteractionEnabled property to true. Then you need to attach a gesture to it.
imageView.userInteractionEnabled = true
//now you need a tap gesture recognizer
//note that target and action point to what happens when the action is recognized.
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
//Add the recognizer to your view.
imageView.addGestureRecognizer(tapRecognizer)
Now you still need the function, in this case imageTapped:, which is where the action happens when the gesture is recognized. The gesture that was recognized will be sent as an argument, and you can find out which imageView was tapped from they gestures view property.
func imageTapped(gestureRecognizer: UITapGestureRecognizer) {
//tappedImageView will be the image view that was tapped.
//dismiss it, animate it off screen, whatever.
let tappedImageView = gestureRecognizer.view!
}
In swift 3.0:
imvEditName.isUserInteractionEnabled = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
imvEditName.addGestureRecognizer(tapRecognizer)
And the target method:
func imageTapped(sender: UIImageView) {
print("image tapped")
}
My previous answer is still accurate for adding a tap recognizer to a UIImageView, however it is not sufficient for the purposes of a view that is animating. If you are actually creating a game, you'll likely want to look into SpriteKit, as it is literally tailor made for this. However, this can be accomplished with normal UIKit. I've tested two approaches, which both work.
Both approaches worked far better on an actual device than in the simulator
Approach 1, Simpler yet slightly less precise up to a tenth of second slower in my tests.
Instead of adding a tap gesture to the UIImageView, add the gesture to the superview. Keep a reference to your blocks in an array property, and check if any blocks are intercepted by the tap.
class ViewController: UIViewController {
let size = CGFloat(40)
let xPosition = CGFloat(14)
let options = UIViewAnimationOptions.Autoreverse
var blocks = [UIImageView]()
override func viewDidLoad() {
super.viewDidLoad()
// Adding recognizer to the whole view.
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("screenTapped:"))
view.addGestureRecognizer(tapRecognizer)
blocks.append(createBlock())
}
//changed to return the created block so it can be stored in an array.
func createBlock() -> UIImageView {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -40, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(2, delay: 0.0, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.blocks.append(self.createBlock())
})
return imageView
}
func screenTapped(gestureRecognizer: UITapGestureRecognizer) {
let tapLocation = gestureRecognizer.locationInView(view)
//This is to keep track of the blocks to remove from the array.
//If you remove them as you iterate the array, and you have more than
//one block to remove, you may end up removing the wrong block
//or with an index out of bounds.
var removeBlocks = [Int]()
for (index, block) in enumerate(blocks) {
//this is the frame of the view as we see it on screen.
//unfortunately block.frame is not where we see it making a recognizer
//on the block itself not work.
if block.layer.presentationLayer().frame.contains(tapLocation) {
//Then this block was clicked.
block.removeFromSuperview()
removeBlocks.append(index)
}
}
//get the indexes ro remove backwards so we are removing from
//back to front.
for index in removeBlocks.reverse() {
blocks.removeAtIndex(index)
}
}
}
Approach 2, Best Performance outside of SpriteKit
In approach two, you subclass UIView, and set your main view to it instead of the UIView. You only need to override a single method. I'm storing the block array in the view for convenience.
First the TouchView.
import UIKit
class TouchView: UIView {
var blocks = [UIImageView]()
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// ignoring multiple touches for now
if touches.count == 1 {
if let touch = touches.first as? UITouch {
let touchLocation = touch.locationInView(self)
//This is to keep track of the blocks to remove from the array.
//If you remove them as you iterate the array, and you have more than
//one block to remove, you may end up removing the wrong block
//or with an index out of bounds.
var removeBlocks = [Int]()
for (index, block) in enumerate(blocks) {
//this is the frame of the view as we see it on screen.
//unfortunately block.frame is not where we see it making a recognizer
//on the block itself not work.
if block.layer.presentationLayer().frame.contains(touchLocation) {
//Then this block was clicked.
block.removeFromSuperview()
removeBlocks.append(index)
}
}
//get the indexes ro remove backwards so we are removing from
//back to front.
for index in removeBlocks.reverse() {
blocks.removeAtIndex(index)
}
}
}
}
}
Now the ViewController would look like:
import UIKit
class ViewController: UIViewController {
let size = CGFloat(40)
let xPosition = CGFloat(14)
let options = UIViewAnimationOptions.Autoreverse
var blocks = [UIImageView]()
// computed get only property for conveniece of accessing block array
var touchView:TouchView {
get {
return self.view as! TouchView
}
}
override func viewDidLoad() {
super.viewDidLoad()
touchView.blocks.append(createBlock())
// Do any additional setup after loading the view, typically from a nib.
}
func createBlock() -> UIImageView {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -40, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(2, delay: 0.0, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.touchView.blocks.append(self.createBlock())
})
return imageView
}
}
In my tests, this seems to work pretty good, even with quickly animated "blocks". Again, if you are planning on making a full fledged game, you should really look at SpriteKit. There is a really simple tutorial here that should get you started. If you can remove the accepted answer from my previous answer, that would help others not be lead down the wrong path, unless they only are looking to add a gesture to a non-moving UIImageView.
Instead using a UIImageView, you could try use a Custome UIButton and replace the default button image with yours. Hence, you do not need to add UITapGestureRecognizer for each UIImageView. #JeremyPope 's approach is actually more efficient compare to this approach.
Handling UIButton is pretty simple, its subclass of UIControl so there are properties like hide and enabled and you can manipulate them in the way you want.
What you need to do is to create UIButton programmaticly, just like you they way you creating UIImageView. The way to create them is similar to UIImageView, read more on create UIButton programmaticly in Swift.
The image below is how a custom UIButton looks like.
Leave comments if you need more info.
By Default, image view user interaction is not enabled, so be sure to enable it by setting it to true.
let myImageView = UIImageView()
myImageView.isUserInteractionEnabled = true