Sprite button and icon hover animation - objective-c

I'm trying to create an effect over my custom buttons (SKSpriteNodes) and other UI objects that are also SKSpriteNode subclasses so that when the mouse hovers over them, they expand slightly to indicate that the user is hovering over them. Once the mouse leaves the vicinity of the sprite, the sprite should go back to normal size.
I initially tried this with the mouseMoved method, but undoing the scaling effect is proving to be an issue. Is there a better way to handle this maybe in the subclasses themselves? Ideas?

One way to do this is to simply keep track of the currently hovered node (if any). Something like this.
I've added a method to recursively search a nodes parents to try and find a suitable hoverable parent. SKSpriteNode in my example, but Producer in your case unless I'm mistaken.
class GameScene: SKScene {
var hoverNode: SKSpriteNode?
//Setup
override func mouseMoved(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
if let node = findHoverable(self.nodeAtPoint(location)) {
if (node == hoverNode) { return }
node.removeAllActions()
node.runAction(SKAction.scaleTo(1.2, duration: 0.5))
hoverNode = node
} else {
if let hoverNode = hoverNode {
hoverNode.removeAllActions()
hoverNode.runAction(SKAction.scaleTo(1.0, duration: 0.5))
self.hoverNode = nil
}
}
}
private func findHoverable(node: SKNode) -> SKSpriteNode? {
if let node = node as? SKSpriteNode {
return node
}
if let parent = node.parent {
if let parent = parent as? SKSpriteNode {
return parent
} else {
return findHoverable(parent)
}
} else {
return nil
}
}
}

Related

How to display MKCompassButton with swiftui

I'm using swiftui and I would like to display the compass button.
The map portion of my code is derived from this tutorial:
https://www.morningswiftui.com/blog/build-mapview-app-with-swiftui
I've looked at sample code to display the compass on the map but I have not been able to find an example that I can get working with my swiftui code.
Actually compass is showing, but only when you try to turn your map. If you want to see compass button for all the time, you can add your own button in makeUIView func:
struct RootMapView: View {
var body: some View {
MapView()
}
}
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView()
map.showsCompass = false // hides current compass, which shows only on map turning
let compassBtn = MKCompassButton(mapView: map)
compassBtn.frame.origin = CGPoint(x: 20, y: 20) // you may use GeometryReader to replace it's position
compassBtn.compassVisibility = .visible // compass will always be on map
map.addSubview(compassBtn)
return map
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
}

Create and Respond to a Hyperlink within a NSTableView Text Cell

I have a program that has a NSTableView populated with files to be uploaded. Once the file is sent, the Text Cell with the file's name gets a hyperlink placed into it (the array data is given an NSMutableString with an NSLinkAttributeName attribute). How do I allow users to click this link to open the webpage in their default browser?
After much searching and trying multiple methods, this is what I came up with as a solution.
Creating a custom class that extends NSTableViewCell:
class TableViewCellCursor: NSTableCellView {
internal var active = false
//MARK: - View Life Cycle
override func awakeFromNib() {
superview?.awakeFromNib()
self.createTrackingArea()
}
//MARK: - IBActions
override func mouseEntered(theEvent: NSEvent) {
if (NSCursor.currentCursor() == NSCursor.arrowCursor() && active) {
NSCursor.pointingHandCursor().set()
}
}
override func mouseExited(theEvent: NSEvent) {
if (NSCursor.currentCursor() == NSCursor.pointingHandCursor() && active) {
NSCursor.arrowCursor().set()
}
}
//Informs the receiver that the mouse cursor has moved into a cursor rectangle.
override func cursorUpdate(event: NSEvent) {
if (active) {
NSCursor.pointingHandCursor().set()
}
}
//MARK: - Util
func createTrackingArea() {
var focusTrackingAreaOptions:NSTrackingAreaOptions = NSTrackingAreaOptions.ActiveInActiveApp
focusTrackingAreaOptions |= NSTrackingAreaOptions.MouseEnteredAndExited
focusTrackingAreaOptions |= NSTrackingAreaOptions.AssumeInside
focusTrackingAreaOptions |= NSTrackingAreaOptions.InVisibleRect
var focusTrackingArea:NSTrackingArea = NSTrackingArea(rect: NSZeroRect,
options: focusTrackingAreaOptions,
owner: self, userInfo: nil)
self.addTrackingArea(focusTrackingArea)
}
}
Checking first responder status when the NSTableView selection changes. This is necessary because the table's selection can be changed, even when it is not the firstResponder:
func tableViewSelectionDidChange(aNotification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
func changeSelectedRowTextColorTo(selectedColor: NSColor, unselectedColor: NSColor) {
let selectedRows = filesToTransferTable.selectedRowIndexes
for (index, tableEntry) in enumerate (tableData) {
if tableData[index]["FileName"] is NSMutableAttributedString {
var name = tableData[index]["FileName"] as! NSMutableAttributedString
var range = NSMakeRange(0, NSString(string:name.string).length)
name.beginEditing()
name.removeAttribute(NSForegroundColorAttributeName, range: range)
if (selectedRows.containsIndex(index)) {
name.addAttribute(NSForegroundColorAttributeName, value:selectedColor, range:range)
} else {
name.addAttribute(NSForegroundColorAttributeName, value:unselectedColor, range:range)
}
name.endEditing()
tableData[index]["FileName"] = name
}
filesToTransferTable.reloadDataForRowIndexes(NSIndexSet(index: index), columnIndexes: NSIndexSet(index:0))
}
}
Adding KVO for checking when FirstResponder changes:
//This is somewhere in your code where you initialize things
//KVO for first responder behavior regarding tableView and updating attributedStrings' colors
self.addObserver(self, forKeyPath: "firstResponder", options: NSKeyValueObservingOptions.Old | NSKeyValueObservingOptions.New, context: nil)
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if (change[NSKeyValueChangeNewKey] is NSTableView) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else if (change[NSKeyValueChangeOldKey] is NSTableView) {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
Finally, checking if the main window (the app itself) is in focus (if this is not done, then the colors won't change appropriately when the window loses focus):
//Put these in the same place as the KVO code
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidBecomeKey:",
name: NSWindowDidBecomeKeyNotification , object: self)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidResignKey:",
name: NSWindowDidResignKeyNotification , object: self)
func windowDidBecomeKey(notification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
func windowDidResignKey(notification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
Text fields automatically support clicking on embedded links, but only if they are at least selectable (if not editable). So, set your text field to be selectable.

Moving object to location of touch

How can I move the 'player' position to be where the user touches the screen? I want them to be able to move it constantly by tapping different places. This is what I have so far but it's not working:
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "Spaceship")
override func didMoveToView(view: SKView) {
// 2
backgroundColor = SKColor.whiteColor()
// 3
player.position = CGPoint(x: size.width/2, y: size.height/2)
// 4
addChild(player)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let player = SKSpriteNode(imageNamed:"Spaceship")
player.position = location
}
}
In the line let player = SKSpriteNode(imageNamed:"Spaceship") in touchesBegan you are creating a new instance of the spaceship node.
This node is not in the scene and setting the position of the new node does not change the position of the existing player node.
Just try resetting the position of you player node instead.
Try
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch?
if let location = touch?.locationInNode(self)
{
player.position = location
}
}

Swift unique property in multiple instances

I know that theoretically it's possible to create multiple instances of the same class with a property that would have a different value for each instance.
The thing is, I can't make it happen.
Each time I'm creating a new instance, it gets the property's value of the other instances, and when I'm changing one value for an instance, it changes the other's too.
So my guess is that I'm doing something wrong (obviously), like accessing the class property value instead of the instance property value... Here's the code.
class CustomUIImageView: UIImageView {
var someParameter: Bool = false // This is the property I want to be different in each version of the instance.
}
class ClassSiege: UIViewController, UIGestureRecognizerDelegate {
var myView: CustomUIImageView! //the instance declaration.
// I use this gesture recognizer to find out the value of the instance I'm tapping on.
func handleTap (sender: UITapGestureRecognizer) {
print("value of someParameter \(self.myView.someParameter)")
}
func handlePan(recognizer: UIPanGestureRecognizer) {
let iv: UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x // 'Blackboard' is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
if centerRondY - centerBoardY < 100 {
self.myView.someParameter = true // If the distance between myView and the blackboard is under 100 I want the instance's property to become true.
} else {
self.myView.someParameter = false // On the other hand, if the distance is greater than 100, I want it to be false.
}
}
// When the user pushes a button, it triggers this method that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
// some code...
// Here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}
}
So clearly that's not the good way to do it, but what's wrong, and how can it be solved?
Basing my answer on your related question.
If what you want to achieve is initializing a property with a value that you provide, just add a new parameter to the initializer. If for instance you are using the initializer with a CGRect passed in, then you can implement an initializer like this:
class CustomUIImageView : UIImageView {
let someParameter : Bool
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect, someParameter: Bool) {
self.someParameter = someParameter
super.init(frame: frame)
}
}
I hope that this is what you are looking for - let me know otherwise.
I've found the solution, and if you've been facing the same issu, here's how to deal with it.
The secret is to downcast the recognizer.view to take the parameter of the subclass CustomUIImageView.
here's how :
func handleTap (sender: UITapGestureRecognizer) {
println("value of someParameter \(self.myView.someParameter)") //I use this gesture recognizer to find out the value of the instance I'm tapping on.
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let iv : UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x //blackboard is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
var myParameter = recognizer.view as CustomUIImageView //<- this is the key point. Downcasting let you access the custom subclass parameters of the object that is currently moved
if centerRondY - centerBoardY < 100 {
myParameter.someParameter = true //so now I'm really changing the parameter's value inside the object rather than changing a global var like I did before.
} else {
myParameter.someParameter = false
}
}
//when user pushes a button, it triggers this func that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
some code...
//here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}

Hide scrollers while leaving scrolling itself enabled in NSScrollView

I'd like to hide the NSScrollers of NSScrollView. I'm aware of the methods setHasHorizontalScroller: and setHasVerticalScroller:. However, if I disable the scrollers, scrolling in that direction gets disabled as well. I'd like to only hide the scrollers while maintaining the possibility to scroll. Any thoughts on this?
I was able to do this by doing:
[labelsScrollView setHasHorizontalScroller:YES];
[[labelsScrollView horizontalScroller] setAlphaValue:0];
The trick of setting alphaValue to zero has issue as invisible scroller still receives touches. Here is what we did in order to solve this (Swift 4).
class InvisibleScroller: NSScroller {
override class var isCompatibleWithOverlayScrollers: Bool {
return true
}
override class func scrollerWidth(for controlSize: NSControl.ControlSize, scrollerStyle: NSScroller.Style) -> CGFloat {
return CGFloat.leastNormalMagnitude // Dimension of scroller is equal to `FLT_MIN`
}
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
private func setupUI() {
// Below assignments not really needed, but why not.
scrollerStyle = .overlay
alphaValue = 0
}
}
Usage:
private class TabBarScrollView: NSScrollView {
private func setupUI() {
borderType = .noBorder
backgroundColor = .clear
drawsBackground = false
horizontalScrollElasticity = .none
verticalScrollElasticity = .none
automaticallyAdjustsContentInsets = false
horizontalScroller = InvisibleScroller()
}
}
if collectionView.enclosingScrollView?.hasHorizontalScroller == true {
collectionView.enclosingScrollView?.horizontalScroller?.scrollerStyle = .overlay
collectionView.enclosingScrollView?.horizontalScroller?.alphaValue = 0.0
} else {
print("horizontalScroller")
}
Make sure horizontal scroller is selected:
I'm looking for an answer to this as well since setting hasVerticalScroller = false disables trackpad scrolling for me. In the meantime, my horrible hack is to use the following in my NSScrollView subclass:
self.hasVerticalScroller = true
self.scrollerInsets.right = -999999
The scroller is technically visible, it is just way off the edge of the view. :(
Have you checked the NSScrollView Class Reference ?
[scrollView setHasVerticalScroller:NO];
this method doesn't disable scrolling.
If you still do not want to use that method you can also:
[scrollView setHorizontalScroller:nil];
This is my solution from sam's answer above:
[myNSScrollView setHasVerticalScroller:YES];
[_myNSScrollView setScrollerInsets:NSEdgeInsetsMake(0, 0, 0, -99999)];
The scroller will be hidden even during the scrolling process.