Swift - Get mouse click coordinates within a UIElement - xcode

I'm making a Cocoa app for OS X using Swift. The app is supposed to be very simple: basically it displays an image, then listens for mouse clicks within that image and returns the 2D mouse coords based on the origin of the image (not the absolute mouse coordinates). I'm not sure if I described that well, but for example once it registers a mouse click event, it should tell me that the mouse click occurred 23 pixels to the right and 57 pixels down from the 0,0 point of the image (or whatever the units would be).
So far I have this, but all I've been able to do is get it to return the absolute mouse coordinates:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var ImageButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
let fileName = "myTestImage.jpg"
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! + "/TrainingSet/" + fileName
//"/Users/dan/Documents/myTestImage.jpg"
let myImage = NSImage(contentsOfFile: path)
ImageButton.image = myImage
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func ImageButtonClicked(sender: NSButton) {
//let x = sender
//let coords = x.frame
let mouseLocation = NSEvent.mouseLocation();
print( "Mouse Location X,Y = \(mouseLocation)" )
print( "Mouse Location X = \(mouseLocation.x)" )
print( "Mouse Location Y = \(mouseLocation.y)" )
}
}
How would I go about getting the information I need?

What about
sender.convertPoint(mouseLocation, fromView:nil)

Related

How to compute MKMapCamera centerCoordinateDistance programmatically in SwiftUI

I have a SwiftUI MapKitView that follows the pattern in https://www.hackingwithswift.com/books/ios-swiftui/advanced-mkmapview-with-swiftui adapted for macOS. In makeNSView I set the region for the interesting bit of the location I want to display, irrelevant of windows size and I can get this code to zoom appropriately by default whether the NSWindow is more landscape than portrait or vice versa.
func makeNSView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.mapType = .satellite
mapView.pointOfInterestFilter = .excludingAll
let region = MKCoordinateRegion(center: centroid, latitudinalMeters: 1160, longitudinalMeters: 1260)
let fittedRegion = mapView.regionThatFits(region)
mapView.setRegion(fittedRegion, animated: false)
}
It appears thatmapView.region is not actually updated upon the return of setRegion()
The rub is I want to orient the map other than true north, so I have to set a camera.
However, the fromDistance: parameter in creating MKMapCamera has to be computed from the region that was set, but what is the camera's field of view angle to determine how high it needs to be to include the correct extent for the window once the region is set? I basically want the zoom level set the same as via the fittedRegion and want to replicate that in the camera with the changed heading (with pitch at 0)
It appears the MKMapViewDelegate has a mapViewDidChangeVisibleRegion and I think the Coordinator is the delegate in SwiftUI. I can see the region on multiple calls to updateNSView, tho it takes a few calls before its actually set. I suspect setting the camera there will create another updateNSView() call which would pose problems.
How can I orient the map to include a given region extent regardless of window size with the zoom level and heading on initial load (but then lets the user manipulate as they see fit)..
Try the following
func makeNSView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.mapType = .satellite
mapView.pointOfInterestFilter = .excludingAll
let region = MKCoordinateRegion(center: centroid, latitudinalMeters: 1160, longitudinalMeters: 1260)
let fittedRegion = mapView.regionThatFits(region)
DispatchQueue.main.async {
mapView.setRegion(fittedRegion, animated: false)
}
return mapView
}
I think I figured it out.
Responding to func mapViewDidFinishLoadingMap(_ mapView: MKMapView) in the Coordinator and setting the mapView.camera.heading to an appropriate value there does what I'm intending.

Run an SKSpriteNode object along a line drawn with the mouse in Swift

If you let go of the left mouse button, my SKSpriteNode object should move along the line previously "drawn" with the mouse.
My problem is that my sprite node is moving down when I release the left mouse button.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let object: SKSpriteNode = SKSpriteNode(imageNamed: "rabbit")
var actionArray = [SKAction()]
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
physicsWorld.contactDelegate = self
object.setScale(0.2)
object.position = CGPoint(x: size.width / 2, y: size.height / 2)
object.zPosition = 0
object.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "rabbit"), size: object.size)
object.physicsBody?.mass = 0
object.physicsBody?.categoryBitMask = 0
object.physicsBody?.contactTestBitMask = 1
object.physicsBody?.collisionBitMask = 0
addChild(object)
}
override func mouseDragged(with event: NSEvent) {
actionArray.append(SKAction.move(to: event.location(in: self), duration: 1))
}
override func mouseUp(with event: NSEvent) {
object.run(SKAction.sequence(actionArray))
// Remove all actions from 'actionArray'.
actionArray.removeAll()
}
}
I have already tried it as shown below. Now my sprite node moves to the first point, where I pressed down the left mouse button.
override func mouseUp(with event: NSEvent) {
let removeAllFromActionArray = SKAction.run {
self.actionArray.removeAll()
}
actionArray.append(removeAllFromActionArray)
object.run(SKAction.sequence(actionArray))
}
I've already tried view! .Convert (event.locationInWindow, to: view! .Scene!) Instead of event.location (in: self), as indicated in Position of mouse click relative to scene, not window, but the result was the same.
I suspect that the bug lies in override func mouseUp(with event: event).
Where does the error lie or is there another solution?

UIView height constraint animation, which has anchor connection with other views

Xcode swift 4
I have some views that are added dynamically in code one below another.
Each new view top anchor is connected to previous view bottom anchor.
And each view have a button that make view to expand/collapse with animation. Here is button code :
let fullHeight : CGFloat = 240
let smallHeight : CGFloat = 44
let currentHeigth = rootView.frame.size.height //I use this to get current height and understand expanded view or not
let heighCons = rootView.constraints.filter //I use this to deactivate current height Anchor constraint
{
$0.firstAttribute == NSLayoutAttribute.height
}
NSLayoutConstraint.deactivate(heighCons)
rootView.layoutIfNeeded()
if currentHeigth == smallHeight
{
rootView.heightAnchor.constraint(equalToConstant: fullHeight).isActive = true
rootView.setNeedsLayout()
UIView.animate(withDuration: 0.5)
{
rootView.layoutIfNeeded() //animation itself
}
}
else
{
rootView.heightAnchor.constraint(equalToConstant: smallHeight).isActive = true
rootView.setNeedsLayout()
UIView.animate(withDuration: 0.5)
{
rootView.layoutIfNeeded() //animation itself
}
}
This all works perfectly but i have a problem : view that below current expanding view changes it y position immediately with no animation. Its just jumping to previous view bottom anchor, that would be active after animation finish.
So my question is :
1) what is the right way to make height constraint animation, when views are connected to each other by bottom/top animation?
2) my goal is just to make a view that would expand/collapse on button click, maybe i should do it another way?
Here's an approach using Visiblity Gone Extension
extension UIView {
func visiblity(gone: Bool, dimension: CGFloat = 0.0, attribute: NSLayoutAttribute = .height) -> Void {
if let constraint = (self.constraints.filter{$0.firstAttribute == attribute}.first) {
constraint.constant = gone ? 0.0 : dimension
self.layoutIfNeeded()
self.isHidden = gone
}
}
}
Usage
expanview.visiblity(gone: true,dimension: 0)
Example
#IBOutlet weak var msgLabel: UILabel!
#IBOutlet weak var expanview: UIView!
#IBAction func toggleCollapisbleView(_ sender: UIButton) {
if sender.isSelected{
sender.isSelected = false
expanview.visiblity(gone: false,dimension: 128)
sender.setTitle("Collapse",for: .normal)
}
else{
sender.isSelected = true
expanview.visiblity(gone: true,dimension: 0)
sender.setTitle("Expand",for: .normal)
msgLabel.text = "Visiblity gone"
}
}

Clock minute-hand disappears when attempting to rotate it

Modus Operandi:
1) Use an UIImageView of a base Clock Image.
2) Add MinuteHand & HourHand sublayers (containing their respective images) to the UIImageView layer.
Problem: both sublayers disappear when attempting to perform a rotation transformation.
Note: 1) I've removed the 'hour' code & ancillary radian calculations to simplify code.
2) The 'center' is the center of the clock. I had adjusted the coordinates to actually pin the hands to the clock's center.
3) The ViewDidLayoutSubviews() appear to be okay. I got the clock + hands.
class ClockViewController:UIViewController {
private let minuteLayer = CALayer()
#IBOutlet weak var clockBaseImageView: UIImageView!
#IBOutlet weak var datePicker: UIDatePicker!
override func viewDidLayoutSubviews() {
guard var minuteSize = UIImage(named: "MinuteHand")?.size,
var hourSize = UIImage(named: "HourHand")?.size
else {
return
}
var contentLayer:CALayer {
return self.view.layer
}
var center = clockBaseImageView.center
// Minute Hand:
minuteLayer.setValue("*** Minute Hand ***", forKey: "id")
minuteSize = CGSize(width: minuteSize.width/3, height: minuteSize.height/3)
minuteLayer.contents = UIImage(named: "MinuteHand")?.cgImage
center = CGPoint(x: 107.0, y: 40.0)
var handFrame = CGRect(origin: center, size: minuteSize)
minuteLayer.frame = handFrame
minuteLayer.contentsScale = clockBaseImageView.layer.contentsScale
minuteLayer.anchorPoint = center
clockBaseImageView.layer.addSublayer(minuteLayer)
}
Here's my problem: Attempting to rotate the minute hand via 0.01 radians:
func set(_ time:Date) {
minuteLayer.setAffineTransform(CGAffineTransform(rotationAngle: .01)) // random value for test.
}
Before rotation attempt:
After attempting to rotate minute hand:
The hand shifted laterally to the right vs rotate.
Why? Perhaps due to the pivot point?
I think this will solve your problem, Take a look and let me know.
import GLKit // Importing GLKit Framework
func set(_ time:Date) {
minuteLayer.transform = CGAffineTransformMakeRotation(CGFloat(GLKMathDegreesToRadians(0.01)))
}
Note: this solution doesn't solve the issue about rotating a CALayer. Instead, it bypasses the issue by replacing the layer with a subview and rotating the subview via:
func set(_ time:Date) {
minuteView.transform = CGAffineTransform(rotationAngle: 45 * CGFloat(M_PI)/180.0)
}
Here's the result:
Still, it would be nice to know how to rotate a CALayer.

iOS 8 Swift Xcode 6 - Set top nav bar bg color and height

I have looked everywhere and tested all the code snippets posted on Stack, but nothing works for me as I need it to work.
I simply want to set:
Nav bar height
Nav bar bg color in RGB
Nav bar centered logo
I'm working with iOS8, Xcode 6 and Swift.
Many thanks for a clear answer!
This is my code in ViewController.swift
// Set nav bar height
navigationController?.navigationBar.frame.origin.y = -10
// Set nav bar bg color
var navBarColor = UIColor(red: 4 / 255, green: 47 / 255, blue: 66 / 255, alpha: 1)
navigationController?.navigationBar.barTintColor = navBarColor
// Set nav bar logo
let navBarImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
navBarImageView.contentMode = .ScaleAspectFit
let navBarImage = UIImage(named: "navBarLogo.png")
navBarImageView.image = navBarImage
navigationItem.titleView = navBarImageView
After applying the code in the accepted answer, the height doesn't seem to change at all..
It's not an easy job...and I've surveyed several articles online (most of them in Objective-C).
The most useful one is this: http://www.emdentec.com/blog/2014/2/25/hacking-uinavigationbar
But its final solution does not put items in the middle, and it's not in Swift.
So I come up with a workable version in Swift. Hope it helps some people as I was saved so many precious time on SO.
Solution in Swift:
The following code will solve some issues you may have encountered:
The title & items are not placed in the middle of the navigation bar
The title & items would flick when the user navigates between view controllers
You need to subclass the UINavigationBar first, and in your storyboard, select the navigation bar element, and in the "Identity Inspector" tab, set the new class as the Custom Class
import UIKit
class UINavigationBarTaller: UINavigationBar {
///The height you want your navigation bar to be of
static let navigationBarHeight: CGFloat = 64
///The difference between new height and default height
static let heightIncrease:CGFloat = navigationBarHeight - 44
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
let shift = UINavigationBarTaller.heightIncrease/2
///Transform all view to shift upward for [shift] point
self.transform =
CGAffineTransformMakeTranslation(0, -shift)
}
override func layoutSubviews() {
super.layoutSubviews()
let shift = UINavigationBarTaller.heightIncrease/2
///Move the background down for [shift] point
let classNamesToReposition: [String] = ["_UINavigationBarBackground"]
for view: UIView in self.subviews {
if classNamesToReposition.contains(NSStringFromClass(view.dynamicType)) {
let bounds: CGRect = self.bounds
var frame: CGRect = view.frame
frame.origin.y = bounds.origin.y + shift - 20.0
frame.size.height = bounds.size.height + 20.0
view.frame = frame
}
}
}
override func sizeThatFits(size: CGSize) -> CGSize {
let amendedSize:CGSize = super.sizeThatFits(size)
let newSize:CGSize = CGSizeMake(amendedSize.width, UINavigationBarTaller.navigationBarHeight);
return newSize;
}
}
Also on my gist: https://gist.github.com/pai911/8fa123d4068b61ad0ff7
iOS 10 Update:
Unfortunately, this code breaks in iOS 10, there is someone who helps fix it, here you go:
iOS 10 custom navigation bar height
And to be clear, this code is kind of hacky since it depends on the navigation bar's internal structure...so if you decide to use it anyway, be prepared for any upcoming changes that may break this code...
Nav bar height:
In a custom navigation controller subclass...
The trick with this one is to NOT change the actual height of the navigation bar and instead adjust its origin.
func viewDidLoad() {
super.viewDidLoad()
navigationBar.frame.origin.y = -10
}
Nav bar bg color in RGB:
In a custom navigation controller subclass...
func viewDidLoad() {
super.viewDidLoad()
navigationBar.barTintColor = // YOUR COLOR
}
or use the appearance proxy
UINavigationBar.appearance().barTintColor = // YOUR COLOR
Nav bar centered logo
In a custom view controller...
func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: // YOUR LOGO)
}
Great answer from Bon Bon!
In Swift 3 however make sure you replace
let classNamesToReposition: [String] = ["_UINavigationBarBackground"]
with
let classNamesToReposition: [ String ] = [ "_UIBarBackground" ]
Otherwise, it wont work.

Resources