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.
Related
I have a button and a textview defined in a UIView Class. Which will make a networks request when pressed. Should i add the button.addTarget in UIView or UIViewController. what is the MVC way to do it.
class MessageInputView: UIView {
let send_button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
button.setTitle("Send", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
return button
}()
let textView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints=false
textView.clipsToBounds = true
textView.layer.cornerRadius = 19.5
textView.layer.borderWidth = 1
textView.layer.borderColor = UIColor.inputTextViewColor.cgColor
return textView
}()
}
class ChatMessagesViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
let messageInputView = MessageInputView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
messageInputView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(messageInputView)
setMessagesInputViewConstraints()
}
}
If you have custom class for your view element, you should declare IBAction in that class, but the logic should be happening in the view controller (or other responsible class in other architectures).
You can establish connection between view and view controller via delegate pattern or with the help of closures, whichever fits your code better.
Here's the example of the closure:
class CustomView: UIView {
// ...
var buttonHandler: (() -> Void)?
#IBAction func buttonAction(_ sender: UIButton) {
buttonHandler?()
}
// ...
}
class ViewController: UIViewController {
// ...
override func viewDidLoad() {
// ...
customView.buttonHander = { print("User clicked the button") }
}
}
Let's look at the difference between the model, view, and controller.
The view is how the user interacts with the application. It should only be concerned with getting input data from the user and sending output data to the user. Any other operations should be delegated to something that is not the view.
The model is how the application handles its required operations. It should not know or care about how/where the result of those operations is being used. It simply performs a requested action and returns the result to the requester.
The controller is what sets up the communication pathways/adapters between the view and the model. It is the only entity that knows both the view and the model exist; they should not know about the existence of each other or the controller, since they don't have to care about it. All they need is an adapter that allows them to communicate with some external entity, which the controller provides.
Given this information, it makes sense to me that your button, when clicked, should make a request to the thing(s) that handle its requests through its adapter. That adapter routes the request to the model, which performs the operation and returns the result. Thus, the view knows nothing about where its request is going or where the response is coming from, and the model has no idea that the view is the one requesting the action and response. This preserves encapsulation.
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)
}
}
I've made a UIView component, inside has 2 buttons. When a user touch the buttons, I need to call a action method of ViewController.
I've tried to pass a selector, but it crash:
on ViewController
component.leftAction = #selector(ViewController.myAction)
func myAction(sender: UIButton!) {
print("tapped!")
}
on Component.swift
public var leftAction: Selector? {
didSet {
if (leftAction != nil){
self.leftButton.addTarget(self, action: leftAction!, for: .touchUpInside)
}
}
}
How can I do it?
It's crashing because button target is wrong. In your case you are passing target as self which is an instance of Component class. Target must be your ViewController instance because your selector is defined in your ViewController class.
You can do something like this to fix your problem.
public var leftTargetAction: (Any, Selector)? {
didSet {
if let targetAction = leftTargetAction {
self.leftButton.addTarget(targetAction.0, action: targetAction.1, for: .touchUpInside)
}
}
}
And use it like this.
component.leftTargetAction = (self,#selector(ViewController.myAction))
Better approach would be to handle this with delegates.
To know more about delegate you can go through this post.
Just change
component.leftAction = #selector(ViewController.myAction)
to
component.leftAction = #selector(ViewController.myAction(sender:))
And ViewController should not be deallocated.
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
}
}
It's like this:
var i = 0
button.addTarget(self, action:"showContent", forControlEvents: UIControlEvents.TouchUpInside)
and the function "showContent" is like this:
func showContent(i: Int) {
//do sth.
}
I want to pass the variable i to function showContent when that button be touched, how could i do ?
In the target-action pattern, you can't do that. You can either use "showContent", in which case your function will be:
func showContent() {
}
… or you can add a colon ("showContent:") in which case your function will be:
func showContent(sender : UIButton) {
// do something with sender
}
This helps enforce the broader Model / View / Controller pattern by making it difficult for your views to be "smart". Instead, your model and controller objects should handle i. A button simply displays what it's told, and tells your controller when it's tapped.
You can read i, and respond to it, in the appropriate method. For example:
class ViewController : UIViewController {
var i = 0
var button = UIButton(type: .System)
var label = UILabel()
override func viewDidLoad() {
button.addTarget(self, action:"showContent", forControlEvents: UIControlEvents.TouchUpInside)
}
func showContent() {
i++
label.text = "Current value of i: \(i)"
}
}