Swift: Recently clicked view? - swift

Is there a property of UIView that returns the most recently clicked (or possibly otherwise "gestured") subview?
I want to have a computed property currentPosition that simply gets a the position property (a property I've defined for my subclass of UIView) from the last-clicked view.
Right now I have an optional instance property let clickedView = UIView?, and inside my gesture handlers I say clickedView = gestureRecognizer.view
and then I have the computed instance property declared as:
currentPosition: Position? { return clickedView?.position } but it would be great to have this be simpler, and especially not have to assign clickedView = gestureRecognizer.view every time.

Possible you can play with tag value. Whenever the touch event is happened loop all the subview and set the tag to zero and set the new touched view tag to some value like 100.
touch event
->set all view tag's to zero
->set the latest view tag's to 100
Loop all the subview
->if tag value is 100 that's the latest view

Related

Tracking the position of a NSCell on change

I have a NSTableView and want to track the position of its containing NSCells when the tableView got scrolled by the user.
I couldn’t find anything helpful. Would be great if someone can lead me into the right direction!
EDIT:
Thanks to #Ken Thomases and #Code Different, I just realized that I am using a view-based tableView, using tableView(_ tableView:viewFor tableColumn:row:), which returns a NSView.
However, that NSView is essentially a NSCell.
let cell = myTableView.make(withIdentifier: "customCell", owner: self) as! MyCustomTableCellView // NSTableCellView
So I really hope my initial question wasn’t misleading. I am still searching for a way how to track the position of the individual cells/views.
I set the behaviour of the NSScrollView (which contains the tableView) to Copy on Scroll in IB.
But when I check the x and y of the view/cells frame (within viewWillDraw of my MyCustomTableCellView subclass) it remains 0, 0.
NSScrollView doesn't use delegate. It uses the notification center to inform an observer that a change has taken place. The solution below assume vertical scrolling.
override func viewDidLoad() {
super.viewDidLoad()
// Observe the notification that the scroll view sends out whenever it finishes a scroll
let notificationName = NSNotification.Name.NSScrollViewDidLiveScroll
NotificationCenter.default.addObserver(self, selector: #selector(scrollViewDidScroll(_:)), name: notificationName, object: scrollView)
// Post an intial notification to so the user doesn't have to start scrolling to see the effect
scrollViewDidScroll(Notification(name: notificationName, object: scrollView, userInfo: nil))
}
// Whenever the scroll view finished scrolling, we will start coloring the rows
// based on how much they are visible in the scroll view. The idea is we will
// perform hit testing every n-pixel in the scroll view to see what table row
// lies there and change its color accordingly
func scrollViewDidScroll(_ notification: Notification) {
// The data's part of a table view begins with at the bottom of the table's header
let topEdge = tableView.headerView!.frame.height
let bottomEdge = scrollView.bounds.height
// We are going to do hit-testing every 10 pixel. For best efficiency, set
// the value to your typical row's height
let step = CGFloat(10.0)
for y in stride(from: topEdge, to: bottomEdge, by: step) {
let point = NSPoint(x: 10, y: y) // the point, in the coordinates of the scrollView
let hitPoint = scrollView.convert(point, to: tableView) // the same point, in the coordinates of the tableView
// The row that lies that the hitPoint
let row = tableView.row(at: hitPoint)
// If there is a row there
if row > -1 {
let rect = tableView.rect(ofRow: row) // the rect that contains row's view
let rowRect = tableView.convert(rect, to: scrollView) // the same rect, in the scrollView's coordinates system
let visibleRect = rowRect.intersection(scrollView.bounds) // the part of the row that visible from the scrollView
let visibility = visibleRect.height / rowRect.height // the percentage of the row that is visible
for column in 0..<tableView.numberOfColumns {
// Now iterate through every column in the row to change their color
if let cellView = tableView.view(atColumn: column, row: row, makeIfNecessary: true) as? NSTableCellView {
let color = cellView.textField?.textColor
// The rows in a typical text-only tableView is 17px tall
// It's hard to spot their grayness so we exaggerate the
// alpha component a bit here:
let alpha = visibility == 1 ? 1 : visibility / 3
cellView.textField?.textColor = color?.withAlphaComponent(alpha)
}
}
}
}
}
Result:
Update based on edited question:
First, just so you're aware, NSTableCellView is not an NSCell nor a subclass of it. When you are using a view-based table, you are not using NSCell for the cell views.
Also, a view's frame is always relative to the bounds of its immediate superview. It's not an absolute position. And the superview of the cell view is not the table view nor the scroll view. Cell views are inside of row views. That's why your cell view's origin is at 0, 0.
You could use NSTableView's frameOfCell(atColumn:row:) to determine where a given cell view is within the table view. I still don't think this is a good approach, though. Please see the last paragraph of my original answer, below:
Original answer:
Table views do not "contain" a bunch of NSCells as you seem to think. Also, NSCells do not have a position. The whole point of NSCell-based compound views is that they're much lighter-weight than an architecture that uses a separate object for each cell.
Usually, there's one NSCell for each table column. When the table view needs to draw the cells within a column, it configures that column's NSCell with the data for one cell and tells it to draw at that cell's position. Then, it configures that same NSCell with the data for the next cell and tells it to draw at the next position. Etc.
To do what you want, you could configure the scroll view to not copy on scroll. Then, the table view will be asked to draw everything whenever it is scrolled. Then, you would implement the tableView(_:willDisplayCell:for:row:) delegate method and apply the alpha value to the cells at the top and bottom edges of the scroll view.
But that's probably not a great approach.
I think you may have better luck by adding floating subviews to the scroll view that are partially transparent, with a gradient from fully opaque to fully transparent in the background color. So, instead of the cells fading out and letting the background show through, you put another view on top which only lets part of the cells show through.
I just solved the issue by myself.
Just set the contents view postsBoundsChangedNotifications to true and added an observer to NotificationCenter for NSViewBoundsDidChange. Works like a charm!

tvOS Focus button in a Subview

I am using AVPlayerController and I have added a subview on top of AVPlayerController. As Soon the subview is displayed I want to shift focus from the player to the Subview and focus the UIButton added on top of that Subivew. I have tried preferredfocusview but its a read-only property and I cannot change it. Can anyone pelase assist. Thanks a lot.
First of all, preferredFocusView is deprecated so you should use preferredFocusEnvironments instead.
It is a computed property, which means you don't assign to it, you override it:
override var preferredFocusEnvironments: [UIFocusEnvironment] {
//if subview exists, return the subview or the button, whichever is focusable
return [subview]
//otherwise use the default implementation of AVPlayerController
return super.preferredFocusEnvironments
}
You should also call self.setNeedsFocusUpdate() and self.updateFocusIfNeeded() to request a focus update when you add the subview.

Looking for an "Zoom"-effect event handler for UICollectionViewCell in tvOS

I have to do something in case of a UICollectionViewCell will be zoom out (hover state).
I thought that this effect updates the bounds of the cell and I've overridden the bound variable.
But the didSetmethod will be only triggered for the first time (original size) and not for the new size during the zooming.
override var bounds: CGRect
{
didSet
{
// do some magic
}
}
What would be the correct way catch this kind of event?
I think it is just the transform property of the view that changes, this would not trigger a bounds change, so perhaps watch the transform property or the transform property of the view's layer. The problem with a layer transform change is that you probably may not observe a change there.

EXC_BAD_INSTRUCTION with Swift UIButton

I am using below code in a function to handle all UIButtons in my View and it works fine when all the objects in the View are UIButton.
for v in self.mainView.subviews as [UIButton]
{
println (v.tag)
}
But in case there is any other objects like Label or ImageView in the same View, I get error 'EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subdued=0x0)'.
How should I change the code so that it works only for (all) UIButton.
The subview property of UIView is an array of AnyObject. The runtime happens because you are performing a forced downcast from [AnyObject] to [UIButton].
From a compiler perspective, the down cast is legit:
let x: [UIButton] = [AnyObject]() as [UIButton]
because AnyObject can be any class, and UIButton is a class.
In your case you are making the assumption that all objects contained in subviews are instances of UIButton, which can be possible (if you explicitly add UIButtons only to the view), but if the view has other UI elements (labels, other views, etc.) then the downcast will fail at runtime.
If for example the view contains another view, the above downcast is equivalent to doing this:
var view = UIView()
var button = view as UIButton
which fails because a UIView is not a UIButton (although thanks to polymorphism the opposite is true, being UIButton inherited from UIView).
If you want your code to print the tag for all UIButtons, ignoring all other UI elements, then #rahul_send89's answer is the correct one: it loops through all elements of the subview property, and it prints the tag only if the current element is a UIButton.
#SteveRosenberg's answer instead print the tag for all elements, regardless of their actual type.
Depending on what you want to do with your buttons (I presume the code posted in your question is just placeholder to explain the problem), there's an alternate way: filtering all buttons from the subviews property and storing into an array:
var buttons = mainView.subviews.filter { $0 is UIButton } as [UIButton]
and do whatever you need with this array of UIButton, such as printing their tag.
for button in buttons {
println(button.tag)
}
This worked for me:
for v in self.view.subviews as [AnyObject]
{
println (v.tag)
}
for v in self.mainView.subviews as [AnyObject]
{
if v is UIButton{
println (v.tag)
}
}

How can I use didSet to change the text of a UITextField (IBOutlet)?

I'd like to set text value of UITextField (IBOutlet) in the DidSet of my model object that I pass.
Here the code:
let manageSettingViewController: ManageSettingViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ManageSettingViewController") as ManageSettingViewController
self.navigationController?.pushViewControllerCustom(manageSettingViewController)
manageSettingViewController.setting = setting
And in the didSet of manageSettingViewController:
var setting: Setting? {
didSet
{
keyTextField.text = setting?.label
valueTextField.text = setting?.value
}
How can I set the text? Because in this case Xcode crash because "keyTextField is nil" :(
You're setting manageSettingViewController.setting right after instantiating manageSettingViewController -- at this point, it hasn't loaded its view from the nib/storyboard yet, so all of its IBOutlet variables (which presumably keyTextField and valueTextField are) are still nil. Those text fields are hooked up as of when ManageSettingViewController's viewDidLoad method is called.
You could change your didSet to check the optional outlets before setting them, or assign through optional chaining:
didSet {
keyTextField?.text = setting?.label
valueTextField?.text = setting?.value
}
This would avoid the crash, but it would also fail to change your text field's content. You'd have to also implement viewDidLoad for ManageSettingViewController to check its setting property and set its text fields accordingly.
Of course, that would duplicate the code from your didSet. That code might still be useful if you want to set setting from elsewhere and have the UI update automatically, but didSet won't help you for updating UI before the UI loads.