How to get action of button in Swift MacOS - swift

I am trying to set up zoom for a game where you click on a button to zoom in, and another to zoom out (which will be part of the UI).
I am having problems getting the input of the buttons to the code. I can read that the button is not pressed, but I cannot figure out how to read if it is pressed.
Looking through the API reference gave me the idea to try using the "state" of the button, but it always returns 0, even when the button is pressed. This could be because the update function does not update as fast as the button's state changes.
Here is my code:
class GameScene: SKScene {
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 0, y: 0)
setUpUI()
}
func setUpUI(){
let button1Rect = CGRect(x: 20, y: 80, width: 80, height: 40)
let zoomIn = NSButton(frame: button1Rect) //place a button in the Rect
zoomIn.setButtonType(NSMomentaryPushInButton)
zoomIn.title = "Zoom In"
zoomIn.alternateTitle = "Zoom In"
zoomIn.acceptsTouchEvents = true
view?.addSubview(zoomIn) //put button on screen
let button2Rect = CGRect(x: 20, y: 30, width: 80, height: 40)
let zoomOut = NSButton(frame: button2Rect) //place a button in the Rect
zoomOut.setButtonType(NSMomentaryPushInButton)
zoomOut.title = "Zoom Out"
zoomOut.alternateTitle = "Zoom Out"
zoomOut.acceptsTouchEvents = true
view?.addSubview(zoomOut) //put button on screen
if zoomIn.state == 1{ //this loop is never true no matter how many times I press the button
print("zoom")
}
}
}
I did cut out some of the code involving the mouse as it is not relevant.

Answer is from TheValyreanGroup.
Simply define the button outside of the setUpUI() function (outside of the GameScene or within it works, outside of the GameScene is easier).

Related

How to show NSPopover without dismissing the first responder?

I'm working on a mac app that has similar functionalities to the rocket app.
Whenever the user types : outside of my app I'll show an NSPopover with NSTableView near the cursor.
The problem I'm facing here is that The moment I show the NSPopover it becomes the first responder and the user couldn't continue typing.
So I tried this, Showed the NSPopover without making a first responder, and used Global listener to listen UpArrow, DownArrow and Enter actions to navigate and select results.
The downside of this approach: Let's say I'm composing a new mail, and the cursor responding to the up/down arrow events, eventually moved to other lines.
func showWidget(at origin: CGPoint, height: CGFloat) {
let popUpView = NSView()
let window: NSWindow = .getWindow(with: .init(width: 1, height: 1), origin: origin, withBorder: false)
window.contentView?.setBackground(color: .white)
window.contentView?.addSubview(popUpView)
popUpView.alignEdges()
popUpView.setBackground(color: .white)
window.makeKeyAndOrderFront(nil)
popover.contentViewController = widgetController
popover.behavior = .semitransient
popover.contentSize = .init(width: .inputWidgetSize.width, height: 200)
popover.animates = false
popover.appearance = NSAppearance.init(named: .darkAqua)
popover.show(relativeTo: .init(origin: .init(x: .zero, y: 0), size: .init(width: .inputWidgetSize.width, height: height)), of: popUpView, preferredEdge: .maxY)
}
Attaching video for reference:
The functionality that I'm expecting
From my app
Note: Notice the cursor position to understand what going wrong.

iOS Swift - Position subview based on button frame (button is inside Vertical Stack View)

Update
After adding convert method between rect and main View, the Y position is ok, but X coordinate is shifted to the right outside of the main view:
Dropdown view(subview) is off main view
Below is button frame before and after convert method. Main view is 414 x 896. Dropdown menu somehow shifts to the right as on attached image.
button frame in stackView btnRect: (120.66666666666666, 0.0, 293.3333333333333, 30.0)
button frame in main view: cvtRect (241.33333333333331, 190.0, 293.3333333333333, 30.0)
view: Optional(>)
Goal. I want to make a dropdown list by showing UIView with dropdown options below a button. Button is inside of Vertical Stack View. I add a TableView with dropdown options to this dropdown UIView.I want to click a button and have this UIView with TableView inside to show just below the button. Basically following this tutorial https://www.youtube.com/watch?v=D3DCPaEE4hQ with the exception that my button is inside of Vertical Stack View.
Issue. UIView and TableView inside UIView show up ok when button is clicked. The issue is dropdown UIView's location that is always the same origin X=120, Y=0.
This is how I try to do it:
I have Vertical Stack with 4 rows
In 4th row I have label(width=120 almost same as X coordinate above) and a button that triggers UIView to show
I am using button to show dropdown list(basically UIView) that should appear just below button when the button is tapped, but it always appears at origin x=120 Y=0 , basically pinned to top of the right column in Vertical Stack View. Vertical Stack View has 1st column with labels, and second column with different controls like buttons etc.
func addTransparentView(frames: CGRect)
{
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
transparentView.frame = window?.frame ?? self.view.frame
//some of the stuff I tried to at least centre dropdownUIView
//transparentView.center.x = window?.center.x ?? self.view.center.x
//transparentView.center.y = window?.center.y ?? self.view.center.y
self.view.addSubview(transparentView)
tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.width, width: frames.width, height: 0)
//some of the stuff I tried to at least centre UIView
//tvPriority.center = verticalStackView.convert(verticalStackView.center, from:tvPriority)
//tvPriority.center.x = view.center.x
//tvPriority.center.y = view.center.y
self.view.addSubview(tvPriority)
tvPriority.layer.cornerRadius = 5
transparentView.backgroundColor = UIColor.black.withAlphaComponent(0.9)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeTransparentView))
transparentView.addGestureRecognizer(tapGesture)
transparentView.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {self.transparentView.alpha = 0.5
self.tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.height, width: frames.width, height: 193)
}, completion: nil)
}
To successfully make dropdown list I need UIView to show up just below buttons frame(X, Y, width, height). But although button is in the 4th row which should be position with much higher Y value, buttons frame is always at X=120, Y=0, so my UIView is always pinned to this location way above button that is supposed to simulate dropdown.
Questions
1. What am I missing with positioning of the dropdown UIView? Why is buttons position Y=0 when the button is in 4th row of Vertical Stack View, with obviously much higher Y position? I also tried to simply centre this dropdown in the centre of screen but that also does not work.
2. I transitioned to iOS development from the world of web development, and I used dropdown a lot in my career. Should I just use Picker View instead? Or alert? What is the most common and most standard way of offering list of mutually exclusive options to user in Swift app?
Thanks a lot
Your button is a subview of the stackView, so its frame is relative to the frame of the stackView.
To get its frame (rect) in the view's coordinate space, you'll want to use .convert. Assign this action to one of your buttons in the stackView:
EDIT Fixed the code example... I had not checked it before posting.
class ConvertViewController: UIViewController {
#IBOutlet var dropDownView: UIView!
#IBAction func didTap(_ sender: Any) {
guard let btn = sender as? UIButton else {
fatalError("Sender is not a button!")
}
guard let sv = btn.superview as? UIStackView else {
fatalError("Sender is not in a stackView!")
}
let btnRect = btn.frame
let cvtRect = sv.convert(btn.frame, to: view)
print("button frame in stackView:", btnRect)
print("button frame in main view:", cvtRect)
let dropDownRect = dropDownView.bounds
let cvtCenterX = cvtRect.origin.x + (cvtRect.size.width / 2.0)
let viewX = cvtCenterX - (dropDownRect.width / 2.0)
let newOrigin = CGPoint(x: viewX, y: cvtRect.minY + cvtRect.height)
dropDownView.frame.origin = newOrigin
}
}
If you look at the output in the debug console, you should see something like this:
button frame in stackView: (0.0, 114.0, 46.0, 30.0)
button frame in main view: (164.5, 489.5, 46.0, 30.0)
As you can see, the rect (frame) of my 4th button in my stackView has an origin of 0.0, 114.0, but after converting it to my view coordinate space, the rect's origin is 164.5, 489.5.
You can now position your "dropdown list" relative to the converted rect.
As a side note, you may want to look at UIPopoverPresentationController.

Not able to select pause button I have added to the screen

My game is setup with a background, a road in the middle of it, then a bar at the top which displays the score and pause button. zPositon of the road and background are 1. The bar at the top zPosition is 4, and the pause button zPosition is 4 as well.
Code for road and background.
road.position = CGPoint(x: CGFloat(0), y: CGFloat(0))
road.zPosition = 1
road.name = roadName
background.position = CGPoint(x: CGFloat(0), y: CGFloat(0))
background.zPosition = 1
background.name = backgroundName
addChild(background)
addChild(road)
Code for the bar at the top.
topBar = SKSpriteNode(color: .black, size: CGSize(width: screenWidth, height: CGFloat(150)))
topBar.position = CGPoint(x: 0, y: (screenHeight/2) + topBar.size.height/2)
topBar.zPosition = 4
topBar.name = topBarName
addChild(topBar)
Code for the pause button.
pauseButton.position = CGPoint(x: (-topBar.size.width/2) + (pauseButton.size.width + (pauseButton.size.width/2)), y: 0)
pauseButton.zPosition = 4
pauseButton.name = pauseButtonName
topBar.addChild(pauseButton)
In my touches began function, I attempted to search for the nodes at the position of the touch, but it only returns the names of the road and background. It doesn't even pick up the topBar or pause button.
for node in nodes
{
print(node.name)
if node.name == pauseButtonName
{
print("paused")
}
}
This will output
Optional("Road")
Optional("Background")
I also tried this method, but "paused" was never returned.
if pauseButton.contains(touch.location(in: self))
{
print("paused")
}
I think it may be important to note that the top bar is positioned off the screen initially. When you start the game, it runs an SKAction to move the bar on screen.
topBar.run(SKAction.moveTo(y: (screenHeight/2) - (topBar.size.height/2), duration: 0.5))
Why am I not able to select the pause button, and how can this be resolved?
I resolved the issue by using this code. Instead of checking for the location in the game scene, I checked for it in the topBar.
if pauseButton.contains(touch.location(in: topBar))
{
print("paused")
}

Xcode how to add buttons to an image view

What approach would I take to add buttons to an image in xcode? The app is for iPhones and iPads so it needs to be "responsive" somehow. I am trying to do something like this image has sample image map I found a Github project that uses Obj-C, but I am just trying to learn app development and have no idea what I am doing. Eventually, once the button is pressed I will make some kind of popup with an image and a little info. My app is in swift. I would appreciate and tips or direction to tutorials.
Assuming that provided Map is one flat image.
This is sudo and it will explain you the concept.
Works for both iPhone or iPad on different screen sizes.
//make sure you have the correct size of original image.
let originalImageSize: CGSize = CGSize(width: 1000, height: 500)
//Lets create a struct to have the basic information we need
struct Item {
var id: Int
var name: String
var x: Double
var y: Double
}
//Creating some sample button on the map
//x and y are based on the original Image size
let buttonsInfo : [Item] = [
Item(id: 1, name: "France", x: 10, y: 10),
Item(id: 2, name: "Poland", x: 20, y: 30),
Item(id: 3, name: "Location A", x: 50, y: 100),
Item(id: 4, name: "Location B", x: 300, y: 300)
]
//sudo code : plug it with your project real image code
let yourUIImageView = UIImageView(image: UIImage())
//after the image is rendered on UI get the real dimensions
//Size of the Image that is rendered on UI in proper aspect ratio
let renderedImageSize: CGSize = CGSize(width: 500, height: 250)
for item in buttonsInfo {
let button = UIButton()
button.tag = item.id
//
button.frame = CGRect(x: Double(renderedImageSize.width/originalImageSize.width)*item.x,
y: Double(renderedImageSize.height/originalImageSize.height)*item.y,
width: 50, height: 50)
button.backgroundColor = UIColor.red
button.setTitle("Name your Button ", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
//yourUIImageView is actual view in your code
yourUIImageView.addSubview(button)
}
func buttonAction(sender: UIButton!) {
let item:Item = buttonsInfo.filter{$0.id == sender.tag}
print("Button tapped")
print(item)
}
Note: if you are resizing your yourUIImageView on orientation changes, then you need to do few more changes to move the buttons based on the new aspect ratio. (let us know if you need help)
You can use UITapGestureRecognizer over the image view to perform same as button action
1st Put in viewDidLoad
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourViewControllerName.imgAction))
ImageViewName.addGestureRecognizer(tap)
2nd write function to perform action
func imgAction(){
// perform the action here
}
There are two approaches
1. You can add image to a button as shown in the picture
You can place an image in the storyboard and then add a button above it, then remove the text of the button. It's better to add both these things in a UIView so you can make sure that the button is exactly above the image. You can set the constraints for the UIView after that add the image and button and set the constraints of top, left, right and bottom to 0
Note: Make sure that the button is always above the image so it's clickable. Try to color the button in storyboard in order to be certain.

Is it possible to custom size the fabric twitter login button?

Supposedly, the twitter login button is a subclass of UIButton but none of the methods associated with setting a UIButton(such as setTitle(), setBackgroundColor() etc) object seem to work with the twitter button. Specifically, I'm trying to resize it but nothing seems to work. Here's what I've tried so far in my viewDidLoad()
let twitterLoginButton = TWTRLogInButton(frame: CGRectMake(0, 0, 200, 50))
self.view.addSubview(twitterLoginButton)
I also tried this
let twitterLoginButton = TWTRLogInButton.init(frame: CGRectMake(0, 0, 200, 50))
self.view.addSubview(twitterLoginButton)
Then I tried to create a container UIView for the login button with the above rect parameters and then place the button into it, instead of directly in the UIView controller like so
let twitterLoginButton = TWTRLogInButton()
let twitterLoginButtonParent = TWTRLogInButton(frame: CGRectMake(0, 0, 200, 50))
twitterLoginButtonParent.addSubview(twitterLoginButton)
self.view.addSubview(twitterLoginButtonParent)
let twitterLoginButtonSize = CGSize(width: 200, height: 50)
twitterLoginButton.sizeThatFits(twitterLoginButtonSize)
At least this time, the button doesn't get stuck in the top left hand corner but is forced to assume the x and y positions of its new parent. However it still cant be forced to assume the size of its parent as I intended.
Is there any way the button can be forced to resize? With the facebook button, it's much easier.
This solved it. Apparently I had to first initialize the button with loginCompletion
let twitterLoginButton = TWTRLogInButton(logInCompletion: { session, error in
if (session != nil) {
print("signed in as \(session!.userName)");
} else {
print("error: \(error!.localizedDescription)");
}
})
twitterLoginButton.frame = CGRectMake(0, 0, 200, 50)