Detect when mouse hovering over a view in a Mac app - swift

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?

Related

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.

Access properties from subview to ViewController Swift

how I can access the properties of my subview to viewcontroller.
example: If I have a #IBOutlet weak var loginBtn: UIButton! how can I access that to my view controller? I did a lot of things and tutorial in how to do that but still I didn't manage to make it work.
And also I'm confuse what's the best way to load xib? there's a lot of way that I saw in tutorial but I want to know also how they do it in production app.
protocol UserLoginDelegate {
func userDidLogin(status: Bool, message: String)
}
#IBDesignable class LoginWidget: UIView {
var loginDelegate: UserLoginDelegate?
var loginView: UIView!
var nibName: String = "LoginWidget"
let defaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var email: UITextField!
#IBOutlet weak var password: UITextField!
#IBOutlet weak var name: UITextField!
#IBAction func loginBtn(sender: AnyObject) {
// init
override init(frame: CGRect) {
super.init(frame: frame)
// set anything that uses the view or visible bounds
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// setup
setup()
}
func setup() {
loginView = loadViewFromNib()
loginView.frame = bounds
loginView.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(loginView)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Controller
class AuthenticationViewController: UIViewController, UserLoginDelegate {
override func viewDidLoad() {
super.viewDidLoad()
loginWidget = LoginWidget(frame: CGRect(x: 0, y: 171, width: 375, height: 247))
authenticationSegment.setEnabled(true, forSegmentAtIndex: 0)
loginWidget.loginDelegate = self
self.view.addSubview(loginWidget)
// Do any additional setup after loading the view.
}
}
Just first, about SO: The goal here is to sort of have a crowd sourced knowledge base(an ontology!) that follows a pattern. Regular users like questions in the form of:
I'm confused about this. I want to accomplish this very specific thing. Here is my code that I've tried. I think this is the problem. Can you tell me how to do this properly or make it work?
So the in the end we get a long list of indexed searchable issues with direct solutions. These sort of wishy washy, non specific, multi-question, confusing posts tend to get downvoted, disregarded, told to rephrase, etc etc.
I'll address your questions:
If I have a #IBOutlet weak var loginBtn: UIButton! how can I access
that to my view controller? I did a lot of things and tutorial in how
to do that but still I didn't manage to make it work.
With a UIButton, or any UIControl subclass, you can drag an #IBAction into your view controller. Accomplish this by ctrl dragging from the storyboard button in question to your code(same way you made the outlet), and selecting Action from the drop down. Then you can optionally change the type of object sending this action (Is it a UIButton, or can anyone call this method, it just happens to be linked to the button), or you can change the control event that it it gets called for (ie is it when they touch DOWN on the button or touch up while the finger is still touching inside the button, this second is the default.)
More generally, if you have a complicated view that is not a control, you can always add a gesture recognizer to it(ie a UITapGestureRecognizer) and drag an IBAction from that, (drag the gesture recognizer onto the view, then from the left pane, ctrl drag from the gesture rec. to your code and select action again).
Finally, and this is more than you need currently, but if you have a view that must inform the view controller of something, or to tell the view controller(or any object) to do something like "send me data please", you can use the delegate pattern. Just briefly: You define a protocol to list the methods and variables needed by the protocol. You make your delegate object conform to this protocol by giving each method an implementation, then you put a variable in your view like
weak var delegate: MyProtocol?
The delegate pattern is important in Swift, and you should probably read up on it. You have probably worked with it if you've used a table view, for instance.
And also I'm confuse what's the best way to load xib? there's a lot of
way that I saw in tutorial but I want to know also how they do it in
production app.
Here's how I start every view that is a xib file's owner. I believe this is the proper way and would appreciate correction if this is wrong. This is swift 3, which you should be using at this point.. it's much better!
#IBOutlet var mainView: UIView!
// the above is the outlet from the xib's main view. which I always call main view
override init(frame: CGRect) {
super.init(frame: frame)
combinedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
combinedInit()
}
func combinedInit(){
Bundle.main.loadNibNamed("XibFileName", owner: self, options: nil)
addSubview(mainView)
mainView.frame = self.bounds
// more initialization stuff
}

Tooltip doesn't show up again

I have a Mac app that exclusively live on the menu bar. It has a progress bar and a label. The label shows the percentage of the progress of the task that's being carried out. I want to show more info when the user hovers the mouse pointer over the progress indicator.
When I set the tooltip initially and hover over, it displays without an issue.
But if I head over somewhere and open the menu app again and hover over again, the tooltip doesn't come up. I can't figure out why. Here's my code.
ProgressMenuController.swift
import Cocoa
class ProgressMenuController: NSObject {
#IBOutlet weak var menu: NSMenu!
#IBOutlet weak var progressView: ProgressView!
let menuItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
var progressMenuItem: NSMenuItem!
override func awakeFromNib() {
menuItem.menu = menu
menuItem.image = NSImage(named: "icon")
progressMenuItem = menu.itemWithTitle("Progress")
progressMenuItem.view = progressView
progressView.update(42)
}
#IBAction func quitClicked(sender: NSMenuItem) {
NSApplication.sharedApplication().terminate(self)
}
}
ProgressView.swift
import Cocoa
class ProgressView: NSView {
#IBOutlet weak var progressIndicator: NSProgressIndicator!
#IBOutlet weak var progressPercentageLabel: NSTextField!
func update(value: Double) {
dispatch_async(dispatch_get_main_queue()) {
self.progressIndicator.doubleValue = value
self.progressIndicator.toolTip = "3 out of 5 files has been copied"
self.progressPercentageLabel.stringValue = "\(value)%"
}
}
}
This is a demo app similar to my actual app. So the update() function is called only once and the values are hardcoded. But in my actual app, the progress is tracked periodically and the update() function gets called with it to update the values. The label's percentage value and the progress indicator's value get updated without a problem. The issue is only with the tooltip.
Is this expected behavior or am I missing something?
I ran into the same problem, and realized the issue was that only the currently focused window will display tool-tips, but after my app lost focus, it would never get it back. Focus usually transfers automatically when the user clicks on your window, but it isn't automatic for menu bar apps. Using NSApp.activate, you can regain focus onto your app:
override func viewWillAppear() {
super.viewWillAppear()
NSApp.activate(ignoringOtherApps: true)
}
sanche's answer worked for me as well, but I ended up moving the tool tips to my NSMenuItems instead so I wouldn't have to steal focus from the foreground app. NSMenuItem's tool tips seem to be handled as a special case so the app doesn't need to be focused.
This solution would make the tool tip apply to everything in the menu item and appear next to the menu rather than over it, but it looks like that might not be a problem in your case.

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

Scroll Position of UITextView at the top

I am using swift and want a UITextView to be at the top when the view launches. At the moment when I launch the app the UITextView is scrolled to the end. I have tried looking online and think scrollRangeToVisible might work but do not know how to use it in swift.
import UIKit
class ThirdViewController: UIViewController {
#IBOutlet weak var FunFact: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
FunFact.scrollRangeToVisible(0, 0)
// Do any additional setup after loading the view.
}
}
Add this to your ViewController:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textView.layoutIfNeeded()
textView.contentOffset = CGPoint.zero
}
This scrolls the UITextView to the top. Since it only knows its size, when it is layouted, "layoutIfNeeded" is needed after you changed the text (i changed it in viewDidLoad).
Try this:
var zeroOffset = CGPoint.zeroPoint
FunFact.contentOffset(zeroOffset)
This should bring the offset to 0 (the offset is an indication of how far the current position is from the initial one)