I cannot get my custom button to live preview basic examples and this is putting me off using what would otherwise be a great help to my development (IBDesignable).
My custom button code is as follows:
import Cocoa
#IBDesignable class MyButton: NSButton {
#IBInspectable var name:String = "Bob"{
didSet{
setup()
}
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
override func prepareForInterfaceBuilder() {
setup()
}
func setup(){
self.title = name
self.setNeedsDisplay()
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
}
}
I then drag either a Custom view OR a NSButton onto my canvas (mainmenu.xib) and adjust its class type in the inspector window to MyButton. The inspectable field pops up and there are no errors BUT my custom button does NOT change its name when I change its value in the property panel!
Further, when I drag a custom view onto the canvas all I get is a blank/transparent rectangle in place of a button (after changing the class to MyButton).
Any assistance would be greatly appreciated. This has been driving me nuts!
I had to put the button inside a NSView:
#IBDesignable
public class MyButton: NSView {
#IBInspectable var name: String = "Bob" {
didSet{
button?.title = name
}
}
public var touchUpHandler: (() -> Void)?
private weak var button: NSButton!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
configureView()
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
configureView()
}
private func configureView() {
let button = NSButton(title: name, target: self, action: #selector(didTapButton(_:)))
button.bezelStyle = .regularSquare
button.translatesAutoresizingMaskIntoConstraints = false
addSubview(button)
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: topAnchor),
button.leftAnchor.constraint(equalTo: leftAnchor),
button.rightAnchor.constraint(equalTo: rightAnchor),
button.bottomAnchor.constraint(equalTo: bottomAnchor)
])
self.button = button
}
func didTapButton(_ sender: NSButton) {
touchUpHandler?()
}
}
And then I used it like so:
#IBOutlet weak var myButton: MyButton!
override func viewDidLoad() {
super.viewDidLoad()
myButton.touchUpHandler = { [unowned self] in
self.performSomeAction()
}
}
Related
How to implement callback for example of button tap inside UICollectionViewCell's UIContentView or better how to do it in Combine way?
Cell registration inside UICollectionView:
let cellRegistration = UICollectionView.CellRegistration<MyCell, Item> { (cell, indexPath, item) in
cell.item = item
}
Cell:
class MyCell: UICollectionViewCell {
var item: Item?
override func updateConfiguration(using state: UICellConfigurationState) {
var newConfiguration = MyContentConfiguration().updated(for: state)
newConfiguration.name = item?.title
contentConfiguration = newConfiguration
}
}
Content configuration:
struct MyContentConfiguration: UIContentConfiguration, Hashable {
var name: String?
func makeContentView() -> UIView & UIContentView {
return MyContentView(configuration: self)
}
}
Content view:
class MyContentView: UIView, UIContentView {
let title = UILabel()
private var currentConfiguration: MyContentConfiguration!
var configuration: UIContentConfiguration {
get {
currentConfiguration
}
set {
guard let newConfiguration = newValue as? MyContentConfiguration else {
return
}
apply(configuration: newConfiguration)
}
}
init(configuration: MyContentConfiguration) {
super.init(frame: .zero)
// Create the content view UI
setupUI()
apply(configuration: configuration)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private extension MyContentView {
private func setupUI() {
// UI stuff
}
private func apply(configuration: MyContentConfiguration) {
guard currentConfiguration != configuration else {
return
}
currentConfiguration = configuration
// Set data to UI elements
title.text = title
//etc..
}
}
Inside MyContentView there'll be a button for example. This button should be bound with item, so when it's tapped - some callback or Combine publisher should emit value, which I've got to catch inside my collection view.
How I've implemented this previously:
let cellRegistration = UICollectionView.CellRegistration<MyCell, Item> { (cell, indexPath, item) in
cell.item = item
//Catch value which is bound with associated item
cell.somePublisher
.sink { [weak self] in
guard let self = self else { return }
self.subscribePublisher.send($0)
}
.store(in: &self.subscriptions)
}
Cell:
class MyCell: UICollectionViewCell {
var item: Item?
public private(set) var somePublisher = CurrentValueSubject<Bool?, Never>(nil)
//UI setup, etc..
//Emit value for a button tap gesture
#objc func handleTap() {
somePublisher.send(true)
}
}
Is it possible to implement this behavior using UIContentView & UIContentConfiguration?
I am having a strange problem with UIButton. I have the following custom class:
import UIKit
#IBDesignable class ToggleButton: UIButton {
#IBInspectable var state1Image: UIImage = UIImage()
#IBInspectable var state2Image: UIImage = UIImage()
#IBInspectable var someString: String = ""
private var toogleOn: Bool = false {
didSet {
if toogleOn {
isSelected = true
} else {
isSelected = false
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
print("test")
print("some string is \(someString)")
}
#objc func didToggleButton() {
toogleOn = !toogleOn
}
}
In the interface builder I set the inspectable vars, let's say I set someString to hello. Now when I run the app and view the log the print for the var is "". Also I am unable to set the images. It only uses the default values and will not use the new value that I set. What am I doing wrong here?
Try this:
#IBDesignable class ToggleButton: UIButton {
#IBInspectable var state1Image: UIImage = UIImage() {
didSet {
setup()
}
}
#IBInspectable var state2Image: UIImage = UIImage() {
didSet {
setup()
}
}
#IBInspectable var someString: String = "" {
didSet {
setup()
}
}
override func prepareForInterfaceBuilder() {
setup()
}
private func setup() {
print("test")
// Updating title label as someString to see the update
self.titleLabel?.text = someString
}
private var toogleOn: Bool = false {
didSet {
if toogleOn {
isSelected = true
} else {
isSelected = false
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
#objc func didToggleButton() {
toogleOn = !toogleOn
}
}
UIMenuItem selector method crashes in iOS 11 beta SDK.
-[WKContentView highlightText]: unrecognized selector sent to instance 0x7f85df8f3200
Method Definition:
func highlightText()
{
//
}
I try to add UIMenuItem in WKWebView,
let menuItemHighlight = UIMenuItem.init(title: "Highlight", action: #selector(ContentWebkitView.highlightText))
UIMenuController.shared.menuItems = [menuItemHighlight]
I was also getting this error when I was overriding canPerformAction and checking for my custom selector. In my case I wanted to remove all menu items except for my custom one and the following made this work for me.
class ViewController: UIViewController {
#IBOutlet weak var webView: MyWebView!
override func viewDidLoad() {
super.viewDidLoad()
loadWebView()
setupCustomMenu()
}
func loadWebView() {
let url = URL(string: "http://www.google.com")
let request = URLRequest(url: url!)
webView.load(request)
}
func setupCustomMenu() {
let customMenuItem = UIMenuItem(title: "Foo", action:
#selector(ViewController.customMenuTapped))
UIMenuController.shared.menuItems = [customMenuItem]
UIMenuController.shared.update()
}
#objc func customMenuTapped() {
let yay = "🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪"
let alertView = UIAlertController(title: "Yay!!", message: yay, preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "cool", style: .default, handler: nil))
present(alertView, animated: true, completion: nil)
}
}
class MyWebView: WKWebView {
// turn off all other menu items
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
OK, we finally made it work for Swift 4:
In your WKWebView subclass, add the following property and method:
// MARK: - Swizzling to avoid responder chain crash
var wkContentView: UIView? {
return self.subviewWithClassName("WKContentView")
}
private func swizzleResponderChainAction() {
wkContentView?.swizzlePerformAction()
}
Then, add an extension to UIView (I put it in the same file as my WKWebView subclass, you can make it fileprivate if you'd like)
// MARK: - Extension used for the swizzling part linked to wkContentView
extension UIView {
/// Find a subview corresponding to the className parameter, recursively.
func subviewWithClassName(_ className: String) -> UIView? {
if NSStringFromClass(type(of: self)) == className {
return self
} else {
for subview in subviews {
return subview.subviewWithClassName(className)
}
}
return nil
}
func swizzlePerformAction() {
swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction))
}
private func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) {
if let currentMethod = self.instanceMethod(for: currentSelector),
let newMethod = self.instanceMethod(for:newSelector) {
let newImplementation = method_getImplementation(newMethod)
method_setImplementation(currentMethod, newImplementation)
} else {
print("Could not find originalSelector")
}
}
private func instanceMethod(for selector: Selector) -> Method? {
let classType = type(of: self)
return class_getInstanceMethod(classType, selector)
}
#objc private func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
Now the UIMenuItem works as expected:
But honestly, this really feels like a hack, and I would love Apple to fix this issue :-/
Thanks for Stephan Heilner for his answer: https://stackoverflow.com/a/42985441/4670400
I am having a lot of problems trying to get a couple of variables from one View Controller to the next. How can I do it properly?
Here's my code below. This is the view controller where I want to be able to send the variables RedScoreW and BlueScoreW to the next window. I am asking on HOW TO DO THIS using SWIFT language and specially for WATCHOS apps.
class InterfaceController2: WKInterfaceController {
var RedScoreW = 0
var BlueScoreW = 0
#IBOutlet var WatchRedScoreLabel: WKInterfaceLabel!
#IBOutlet var WatchBlueScoreLabel: WKInterfaceLabel!
#IBAction func RedScorePlus() {
if RedScoreW == 999 {
RedScoreW = 0
WatchRedScoreLabel.setText("0")
}else {
RedScoreW += 1
WatchRedScoreLabel.setText(String(RedScoreW))
}
}
#IBAction func RedScoreMinus() {
if RedScoreW == 0 {
RedScoreW = 999
WatchRedScoreLabel.setText("999")
}
else {
RedScoreW -= 1
WatchRedScoreLabel.setText(String(RedScoreW))
}
}
#IBAction func BlueScorePlus() {
if BlueScoreW == 999 {
BlueScoreW = 0
WatchBlueScoreLabel.setText("0")
} else{
BlueScoreW += 1
WatchBlueScoreLabel.setText(String(BlueScoreW))
}
}
#IBAction func BlueScoreMinus() {
if BlueScoreW == 0 {
BlueScoreW = 999
WatchBlueScoreLabel.setText("999")
}
else {
BlueScoreW -= 1
WatchBlueScoreLabel.setText(String(BlueScoreW))
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
WatchRedScoreLabel.setText(String(RedScoreW))
WatchBlueScoreLabel.setText(String(BlueScoreW))
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
And this is the Destination View Controller where I want to be able to use RedScoreW and BlueScoreW variables.
class InterfaceController3: WKInterfaceController {
#IBOutlet var finalRedScoreLabel: WKInterfaceLabel!
#IBOutlet var finalBlueScoreLabel: WKInterfaceLabel!
#IBAction func DoneAndResetButton() {
self.popToRootController()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
* EDIT *
I am trying to do it this way, this is the code where I send it, check:
#IBAction func FinishButtonPushVariables() {
arrayofScores[0] = RedScoreW
arrayofScores[1] = BlueScoreW
pushControllerWithName("LastScreen", context: arrayofScores)
}
And this is where I receive it... and it doesn't work. LOL
#IBOutlet var finalRedScoreLabel: WKInterfaceLabel!
#IBOutlet var finalBlueScoreLabel: WKInterfaceLabel!
#IBAction func DoneAndResetButton() {
self.popToRootController()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let finalarrayofScores = context as? InterfaceController2
finalBlueScoreLabel.setText(String(finalarrayofScores!.arrayofScores[1]))
finalRedScoreLabel.setText(String(finalarrayofScores!.arrayofScores[0]))
// Configure interface objects here.
}
In iOS apps, we use prepareForSegue to do this. On watchOS apps, we use contextForSegueWithIdentifier to pass a context from one interfaceController to another.
Here is a link to the class reference that will detail more about this. But here are the basics:
There are two different methods that can be used. One is for going from one interface controller to another:
func contextForSegueWithIdentifier(_ segueIdentifier: String) -> AnyObject?
The other is for going from a one interface controller to another when a row in a table is tapped:
func contextForSegueWithIdentifier(_ segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex rowIndex: Int) -> AnyObject?
So one of these two methods will go in the interfaceController that is sending the context, and you will receive that context in the awakeWithContext method of the receiving interfaceController.
Here is a link to a tutorial that will show an application of this process.
EDIT
Here is a specific solution to your problem.
In the interface controller where you send it, put this code:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
arrayofScores[0] = RedScoreW
arrayofScores[1] = BlueScoreW
return arrayOfScores
}
Then in your destination interface controller, put this code:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let finalArrayOfScores = context as? [Int]
if let f = finalArrayOfScores {
finalBlueScoreLabel.setText(String(f[1]))
finalRedScoreLabel.setText(String(f[0]))
}
}
You need to set up variables to hold your variable first.
class YourSecondViewController: UIViewController {
var yourVariable:Double?
}
Then have your button trigger your custom segue. Use your variable as the argument for sender.
class YourFirstViewController: UIViewController {
#IBAction func buttonTapped(sender: AnyObject) {
self.performSegueWithIdentifier("segue", sender: yourVariable)
}
}
Then pass the sender data by overriding the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier = "segue") {
let secondViewController = segue.destinationViewController as YourSecondViewController
let yourVariable = sender as Double
secondViewController.duration = yourVariable
}
}
I guess your problem is that you are passing an array to the context and you cast it as WKIntefaceController.
Try replacing this line
let finalarrayofScores = context as? InterfaceController2
by
let finalarrayofScores = context as? [Int]
i have a subclass of NSView that handles Mouse events, inside that NSView i have a subview (which is another subclass of NSView). How can i handle Mouse Events for both NSViews.
What i want to achieve is the following:
A NSView where i got a character, when i move my mouse around inside that view the character rotate to follow the mouse. inside the same there are some Items, when the mouse hover over an item i want to display some information... how can achieve this?
basically: two classes receive and respond to mouse over.
Best Regards
Kristian
Here is how we did in Swift 5:
class TrackingAreaView: NSView {
private var isMouseOverTheView = false {
didSet {
backgroundColor = isMouseOverTheView ? .red : .green
}
}
private lazy var area = makeTrackingArea()
private var backgroundColor: NSColor? {
didSet {
setNeedsDisplay(bounds)
}
}
init() {
super.init(frame: NSRect()) // Zero frame. Assuming that we are in autolayout environment.
isMouseOverTheView = false
}
required init?(coder: NSCoder) {
fatalError()
}
public override func updateTrackingAreas() {
removeTrackingArea(area)
area = makeTrackingArea()
addTrackingArea(area)
}
public override func mouseEntered(with event: NSEvent) {
isMouseOverTheView = true
}
public override func mouseExited(with event: NSEvent) {
isMouseOverTheView = false
}
private func makeTrackingArea() -> NSTrackingArea {
return NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeInKeyWindow], owner: self, userInfo: nil)
}
open override func draw(_ dirtyRect: NSRect) {
if let backgroundColor = backgroundColor {
backgroundColor.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
}
}
}
i guess, you should play with CreateMouse Region and handle Mouse event like mouseenter , mouse exit on it,
refer following method of NSView
addTrackingRect : provide Rect where you would like to capture mouse event
for that region you would get following event,
mouseDown
mouseUp
mouseEntered
mouseExited
and so on