Swift coordinates of a pressed button - swift

I'm working on a tic tac toe game (3x3), so I have 9 buttons and what I want to do is to get coordinates of a button which user had pressed and insert an image on the place of the button.
Example:
#IBOutlet weak var button1Outlet: UIButton!
#IBAction func button1Action(sender: AnyObject) {
let imageName = "IMG.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 0, y: 0, width: 90, height: 90)
view.addSubview(imageView)
}
I know I can get coordinates of the button using button1Outlet.center , but I need something like this.center to avoid hardcoding it for every button.

You can cast the sender to a UIButton which you then can access any frame related methods on.
(sender as UIButton).center

When a button is clicked it is passed as a parameter to the function. Just use the sender.
// Change sender to a UIButton type
#IBAction func button1Action(sender: UIButton) {
// ... Setup your views
// Set same frames
imageView.frame = sender.frame
view.addSubview(imageView)
// If you want to remove the button
sender.removeFromSuperview()
}

Related

Identifying Objects in Firebase PreBuilt UI in Swift

FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.

Swift UIImage cant be animated with gravity

I've looked into everything on Google, and that is not much. Is the community for Swift really this small???
I have an image, and on a long-press, I want it to fall down with gravity and hit the bottom of the screen.
The error I get is
Cannot convert value of type 'UIView' to expected argument type
'[UIDynamicItem]'
I have tried UILabel, UIImage, UIImageView, Rect, UIView i get this error on what ever i do.
My goal is to use UIImage or UIImageView.
This is the code I'm using for the animation:
var animator: UIDynamicAnimator!
var gravity: UIDynamicBehavior!
var collision : UICollisionBehavior!
var redBoxView: UIView?
#IBOutlet weak var detailsImageWeather: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: self.view)
let imageTap = UILongPressGestureRecognizer(target: self, action: #selector(imageTapped))
detailsImageWeather.addGestureRecognizer(imageTap)
}
#objc func imageTapped() {
var frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
redBoxView = UIView(frame: frameRect)
redBoxView?.backgroundColor = UIColor.red
self.view.addSubview(redBoxView!)
let image = detailsImageWeather.image // This is what i want to use instead of redBoxView
gravity = UIGravityBehavior(items: redBoxView!)
animator.addBehavior(gravity)
collision = UICollisionBehavior (items: redBoxView!)
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)
let behavior = UIDynamicItemBehavior(items: [redBoxView!])
behavior.elasticity = 2
}
What am i doing wrong? cant find any more things to try on google
The error
Cannot convert value of type 'UIView' to expected argument type '[UIDynamicItem]'
tells that you need an array of UIDynamicItem. It's easy to miss the little square brackets.
You're actually configuring your UIGravityBehavior and UICollisionBehavior with redBoxView (an object) instead of [redBoxView] (an array). That's why you get the error.
You need to change your configurations to this
gravity = UIGravityBehavior(items: [redBoxView!]) //redBoxView passed in an array
and this
collision = UICollisionBehavior (items: [redBoxView!]) //redBoxView passed in an array

Add action to button in .xib file Using Swift(4.0)

I implemented horizontal(Paging) slide show of views, created separate xib and integrated with the view controller. Slide show is working as expected but I want add action to the every views so that from there I can move directly to the respective main content pages. Find the below code for implementation of slide show.
func createSlides() -> [Slide] {
let slide1:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide1.labelTitle.text = "Page1"
let slide2:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide2.labelTitle.text = "Page2"
return [slide1, slide2]
Slide Function
func setupSlideScrollView(slides : [Slide]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
Xib file
class Slide: UIView {
#IBOutlet weak var labelTitle: UILabel!
var onClickCallback: (() -> Void)?
override init (frame : CGRect) {
super.init(frame : frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
If I think what you are asking is right then do this. (BTW I am a bit rusty at this so please don't crucify me if I'm wrong). Also I don't know where you are designing this so I am going to assume it is in a different place to main.viewcontroller. If it isn't just add a button and create a segue to the content page.
Make a public var in createSlides Func and set it to the slide you are on. Eg: Slide 1. When slide two is opened it should update the var two slide two. For the sake of this we will call the var test
Add a button into your design file
Add a #IBAction into the viewcontroller. This #IBAction should be linked with your button.
inside this #IBAction create a segue to the content using the variable we created before. self.performSegue(withIdentifier: test, sender: self)
In the main view controller add two segues from the view with the slidshow to the view controller with the content (Assuming you only have two slides). One segue should be called slide 1 (The same name as the let you created in createSlides func.) The second should be called slide 2 (for obvious reasons.)
Now when your button in slide 1 is pressed it should perform the segue slide 1 and show your main content
Hope I helped, Toby.
Also if this doesn't work, is not what you want to achieve or is badly worded please comment what it is and I will try to fix it.
call this function and pass pageIndex where you want to jump:
func jumpToView(_ index : Int) {
let offset = CGPoint(x: view.frame.width * CGFloat(index), y: 0)
scrollView.setContentOffset(offset, animated: true)
}
Hope this what you are looking.
First line in function let offset = CGPoint(x: view.frame.width * CGFloat(index), y: 0), for calculate position where you want to move in scrollview. E.g : for first xib position will be CGPoint(x:0,y:0) and for 3rd xib, position will be CGPoint(x:200,y:0), consider each xib width is 100
setContentOffset use to scroll at specific location/point.
For more check Link

NSTextField not visible

I recently started coding Swift. So I am working on a macOS app to get more used to Swift.
I want that an NSTextField is created when the user presses a button. I got the code below but the NSTextField doesn't display. While research I found that it somehow must be added to view but not how this is done.
#IBAction func buttonPressed(_ sender: Any) {
let label = NSTextField()
label.stringValue = ("Hello World")
}
How do I make the NSTextField display? Any help would be highly appreciated.
Just turn the label into a layer-backed view. Then add it into a visible view:
let rect = NSMakeRect(20, 20, 80, 44)
let label = NSTextField(frame: rect)
label.wantsLayer = true
label.layer?.backgroundColor = NSColor.greenColor().CGColor
window?.contentView?.addSubview(label)
let rect = NSMakeRect(20, 20, 80, 44)
let label = NSTextField(frame: rect)
view.addSubview(label)
Worked for me. In Xcode 10 with swift 4.

How to get action of button in Swift MacOS

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).