I'm trying to create my first Cocoapod framework, and need to attach a simple UITapGestureRecognizer to a view, but I can't get the tap gesture action to be called from within my framework. I've got:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let foo = Foo()
foo.attachTo(view: view)
}
}
I created a framework using pod lib create Foo, inside is
public class Foo {
public init() {}
public func attachTo(view: UIView) {
let endpointGesture = UITapGestureRecognizer(target: self, action: #selector(selected(_:)))
view.backgroundColor = UIColor.blue
view.isUserInteractionEnabled = true
view.addGestureRecognizer(endpointGesture)
}
#objc private func selected(_ sender: UITapGestureRecognizer) {
print("Gesture Recognized")
}
}
I can tell the view is correctly passed into the framework because building the app gives me a blue screen.
Moving the gesture recognizer and selected function into ViewController works as expected as well. Tapping on the view prints Gesture Recognized to the console, so there's something going on with the framework I don't understand.
I have already tried adding the -ObjC linker flag to the framework, but that doesn't seem to have done anything. Am I missing something?
The problem is that your foo variable is not retained.
If you make the foo variable as instance variable it should work.
class ViewController: UIViewController {
let foo = Foo() // this is now retained by the view controller
override func viewDidLoad() {
super.viewDidLoad()
foo.attachTo(view: view)
}
}
Related
I want to prevent certain toolbar items from being removed by the user. They should still be movable, just not removable.
I tried creating a custom subclass of NSToolbar with a custom removeItem(at:) implementation, but it seems this method is not even called if the user drags an item out of the toolbar in the customization palette.
The delegate also doesn't seem to expose functionality for this.
How can I disable removal of certain NSToolbarItems?
I am not sure if you can prevent it from being removed but you can implement the optional toolbarDidRemoveItem method and insert the item that you don't want it to be removed back:
import Cocoa
class WindowController: NSWindowController, NSToolbarDelegate {
#IBOutlet weak var toolbar: Toolbar!
override func windowDidLoad() {
super.windowDidLoad()
toolbar.delegate = self
}
func toolbarDidRemoveItem(_ notification: Notification) {
if let itemIdentifier = (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier,
itemIdentifier.rawValue == "NSToolbarShowColorsItem" {
toolbar.insertItem(withItemIdentifier: itemIdentifier, at: 0)
}
}
}
Since it is not super critical if they are removed in case a private API call would stop working, I opted for the private API solution.
extension NSToolbarItem {
func setIsUserRemovable(_ flag: Bool) {
let selector = Selector(("_setIsUserRemovable:"))
if responds(to: selector) {
perform(selector, with: flag)
}
}
}
This works exactly as advertised.
I'm designing a mac app with Xcode 10 (beta) and I got an issue with the Preference Window Controller
I have in my Main.storyboard a NSWindowController of custom class PreferenceWindowController with a toolbar. Here are its connections :
Here is the full class :
class PreferenceWindowController: NSWindowController, NSWindowDelegate {
#IBAction func didClickAuthor(_ sender: Any) {
print("author")
}
#IBAction func didClickTypo(_ sender: Any) {
print("typo")
}
override func windowDidLoad() {
super.windowDidLoad()
}
func windowWillClose(_ notification: Notification) {
print("willClose")
}
}
The window is initiated via the AppDelegate class with this code :
let storyboard = NSStoryboard(name: "Main",bundle: nil)
if let wc = storyboard.instantiateController(withIdentifier: "PreferenceWindowController") as? PreferenceWindowController
{
wc.showWindow(self)
}
The window opens as expected, with the toolbar clickable, but no functions from PreferenceWindowController are called at all, neither the closing of the window, nor the clicks on the toolbar.
I checked every connections, every class name, and I really don't know what's wrong...
SOLUTION
The solution is to store the PreferenceViewController class inside the AppDelegate class as a variable.
My solution :
var preferenceWindowController:PreferenceWindowController? = nil
#IBAction func clickPreferences(_ sender: Any) {
if let wc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? PreferenceWindowController {
let window = wc.window
preferenceWindowController = wc
wc.showWindow(self)
}
}
Thank you for helping !
The comment above seems like it could be on the right track. Based on the code context you've included in your question, it looks like the window controller you create will only have a lifetime for that function call.
Try making the window controller an instance variable. This is normally how I wire things up in an App delegate that creates window controllers. It's a simple pattern that works well.
I do have a View, in which I embedded a ContainerView. I fill my labels on my ContainerView the first time here
class UpperLower { ...
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
let PlayerInfoHeaderView = segue.destination as? PlayerInfoHeader
PlayerInfoHeaderView?.player1 = player1
PlayerInfoHeaderView?.player2 = player2
PlayerInfoHeaderView?.game = game
}
}
The Segue triggers the viewDidLoad() where I call the method updateUI()
class PlayerInfoHeader: UIViewController { ...
override func viewDidLoad() {
super.viewDidLoad()
updateUI(game: game)
}
func updateUI(game: Player.Game) {
player1NameLabel.text = player1.name
player2NameLabel.text = player2.name
switch game {
case .UpperLower:
player1PointsLabel.text = "Points: \(player1.points.UpperLower)"
player1WinrateLabel.text = "Winrate: \(player1.winrates.UpperLower) %"
player1RoundsWonLabel.text = "Rounds Won: \(player1.roundswon.UpperLower)"
player2PointsLabel.text = "Points: \(player2.points.UpperLower)"
player2WinrateLabel.text = "Winrate: \(player2.winrates.UpperLower)"
player2RoundsWonLabel.text = "Rounds Won: \(player2.roundswon.UpperLower)"
}
Now, after every round played, I also want to update my UI. I tried a lot of things, but I have no clue, how to trigger the UpdateUI() out of my UpperLower manually. I know that I need a reference to my embedded container view. But how can I get this reference outside of the segue context? Is there an easy way to solve my problem?
PS: I did all my UI work on the storyboard.
Set a weak property (to avoid retain cycle) in PlayerInfoHeader to keep a reference to your parent
weak var parentVC: UpperLower?
Set the property in prepareForSegue
PlayerInfoHeaderView.parentVC = self
I have the following code:
extension ViewController {
func AddLeftGesture(){
let SwipeLeft:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyDismissOnSwipeLeft))
self.view.addGestureRecognizer(SwipeLeft)
}
func MyDismissOnSwipeLeft(){
self.dismiss(animated: true, completion: nil)
}
and What I would like to accomplish is that override the viewDidLoad and
call AddLeftGesture method so that it'll be part of each VC I make
and I don't have to type it again and again in each viewDidLoad,
is this possible? or do you guys have any other suggestions?
well I don't think it's a good idea, because typically viewDidLoad is used for setting most properties and if you would like to override it in a view controller you should write it again.What I can suggest is that to make a base ViewController and add this code in the viewDidLoad of that and then subclass every viewController from the base view controller , This way whenever you want to change anything you just call super.viewDidLoad
class BaseViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
addLeftGesture()
}
}
class CustomViewController: BaseViewController{
}
Make this class which inherits UITapGestureRecognizer
open class BlockTap: UITapGestureRecognizer {
fileprivate var tapAction: ((UITapGestureRecognizer) -> Void)?
public override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
}
public convenience init (
tapCount: Int = 1,
fingerCount: Int = 1,
action: ((UITapGestureRecognizer) -> Void)?) {
self.init()
self.numberOfTapsRequired = tapCount
#if os(iOS)
self.numberOfTouchesRequired = fingerCount
#endif
self.tapAction = action
self.addTarget(self, action: #selector(BlockTap.didTap(_:)))
}
open func didTap (_ tap: UITapGestureRecognizer) {
tapAction? (tap)
}
}
then make an extension of UIView
extension UIView {
public func addTapGesture(tapNumber: Int = 1, action: ((UITapGestureRecognizer) -> ())?) {
let tap = BlockTap(tapCount: tapNumber, fingerCount: 1, action: action)
addGestureRecognizer(tap)
isUserInteractionEnabled = true
}
}
Then You can use this as
override func viewDidLoad() {
super.viewDidLoad()
self.view.addTapGesture(action: {[unowned self] (_) in
//Do whatever on click of View
})
}
Hope it helps!
There's two options AFAIK. Either you can subclass UIViewController and then make all of your controllers inherit from the subclassed one, or you can swizzle UIViewController's viewDidLoad().
I personally would choose swizzling, although it has one disadvantage - it hides the implementation and might be confusing for a new developer coming onto a project. So make sure you document this properly, somewhere in your project README and in the code as well.
Now for some code examples:
Subclassing UIViewController
MyViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addGesture()
}
func addGesture() {
// Do what you need
}
}
class OtherViewController: MyViewController {
// Automatically will add gesture because it's a subclass of MyViewController
}
Swizzling viewDidLoad
What method swizzling does is, that it exchanges implementations of your methods. That simply means that the name of your function points at code from a different function. For more information on this topic read this article.
UIViewController+Swizzle.swift
static func swizzle(selector originalSelector: Selector,
with newSelector: Selector,
on targetClass: AnyClass) {
let originalMethod = class_getInstanceMethod(targetClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(targetClass, newSelector)
// If we were able to add the swizzled function, replace methods.
// Otherwise exchange implementations if method already exists.
if class_addMethod(targetClass, originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) {
class_replaceMethod(targetClass, newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension UIViewController {
// This function is getting called automatically by the runtime,
// when this class is loaded to perform some additional intiialization.
// However, this has now been deprecated in Swift, so only option is to
// declare a static function which you need to remember to call from
// somewhere, preferably early in your app initialization, like your
// didFinishLaunching function in AppDelegate or even AppDelegate's init
// function. I kept the initialize function in the code as a reference,
// however you would probably want to write it like in the comment
// below, to silence the warning.
//
// class func swizzle()
//
open override class func initialize() {
if self != UIViewController.self { return }
let swizzlingClosure: () = {
swizzle(selector: #selector(UIViewController.viewDidLoad),
with: #selector(UIViewController.swizzled_viewDidLoad),
on: UIViewController.self)
}()
swizzlingClosure
}
#objc private func swizzled_viewDidLoad() {
// Calls the original implementation,
// because implementations are switched.
swizzled_viewWillAppear(animated)
// Do whatever you need
addGesture()
}
#objc func addGesture() {
// Add your gesture
}
}
I have a ViewController that calls a class to build a menu. This menu draw a button with a buttonClicked method. I call this menu from many different ViewControllers so I need this menu to call a different button method depending on the ViewController it was called from. I cannot think how to program this?
class MenuController : UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
var menu = Menu()
self.view.addSubview(menu)
}
func buttonClicked(sender:UIButton)
{
var tag = sender.tag
println("I want the button click method to call this method")
}
}
class Menu:UIView
{
init()
{
var button:UIButton = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
button.frame = CGRectMake(0,0,280, 25)
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
button.tag = Int(itemNo)
menu.addSubview(button)
}
func buttonClicked(sender:UIButton)
{
var tag = sender.tag
println(tag)
}
}
This is a perfect use case for either a closure or a delegate/protocol:
Closure option
In your Menu class, create a public variable (say buttonCode) that will host your closure:
class Menu:UIView
{
var buttonCode : ()->()
and your buttonClicked function becomes:
func buttonClicked(sender:UIButton) {
self.buttonCode()
}
Then in the controller, you set up menu.buttonCode = { println("hello") }, and that's it.
Protocol option
You create a protocol for your Menu, that expects a buttonCode() function. You also create a var in the Menu class to host the weak reference for the delegate. Your view controller implements the protocol and the buttonCode() function. Then your buttonClicked function becomes:
func buttonClicked(sender:UIButton) {
self.delegate.buttonCode()
}
I personally prefer today to use the closure option, it's cleaner and simpler, at least in this situation. Please see http://www.reddit.com/r/swift/comments/2ces1q/closures_vs_delegates/ for a more in-depth discussion.