Adding UIButton dynamically: action not getting registered - ios

I'm creating pages dynamically, each page contains a Navigation Controller and UIViewController.
Inside each page, there are components like link, images, texts.
Each component is a class like the following:
class link: Component, ComponentProtocol {
var text: String
var url: String
func browseURL(sender: UIButton!){
let targetURL = NSURL(fileURLWithPath: self.url)
let application = UIApplication.sharedApplication()
application.openURL(targetURL!)
}
func generateView() -> UIView?{
var result: UIView?
var y = CGRectGetMinY(frame)
var linkBtn = UIButton(frame: CGRect(x: 0, y:30 , width:300 , height: 50)
linkBtn.setTitle(self.text, forState: UIControlState.Normal)
linkBtn.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
linkBtn.titleLabel?.font = linkBtn.titleLabel?.font.fontWithSize(15)
// this doesn't seem to be registered properly
linkBtn.addTarget(self, action: "browseURL:", forControlEvents: UIControlEvents.TouchUpInside)
result = UIView()
result?.addSubview(linkBtn)
return result
}
Then in the ViewDidLoad of the page ViewController method I would have this to initialise the page components:
for component in components!{
if let acomponent:ComponentProtocol = component as? ComponentProtocol {
if let res = acomponent.generateView(innerFrame) {
if let view = res.view {
self.view.addSubview(view)
}
}
}
}
The button is showing, but when I touch nothing happens. When I debugged, the browseURL is no triggered at all.
What's wrong with my code? I'm guessing because I registered the action in link class and not in the ViewController of the page?
UPDATE
This could be a similar issue, but the answer is not so straightforward and I actually have the component reference in my ViewController: (Target: Object) not working for setting UIButton from outside viewController

I'm not at a Mac right now so I can't test, but does this work:
linkBtn.target = self
linkBtn.action = "browseURL:"
I think if you have selectors in Swift you have to be careful with colons and specifying #objc if your class doesn't inherit from NSObject, it could also be something to do with that?

Related

Swift - open URL from button click

I am learning Swift and iOS development, and I am just trying to figure out how to open an URL from a button click.
I found this answer: SwiftUI: How do I make a button open a URL in safari?
So I am trying to incorporate "Link" into my code below:
class ViewController: UIViewController {
private let visitwebsitebutton: UIButton = {
let visitwebsitebutton = UIButton()
visitwebsitebutton.backgroundColor = .gray
visitwebsitebutton.setTitle("Visit Website", for: .normal)
visitwebsitebutton.setTitleColor(.white, for: .normal)
visitwebsitebutton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
visitwebsitebutton.layer.cornerRadius = 20
visitwebsitebutton.Link("Some label", destination: URL(string: "https://www.mylink.com")!) // <-- link used here
return visitwebsitebutton
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(visitwebsitebutton)
}
}
Using Link above gives me an error that reads "Value of type 'UIButton' has no member 'Link'".
What am I doing wrong and how can I fix it?
Edit 1
I just tried this inside private let visitwebsitebutton:
visitwebsitebutton(action: {"www.redacted.com"})
But now I'm getting the below error:
Cannot call value of non-function type 'UIButton'
Edit 2
Within private let visitwebsitebutton, I attempted the following:
visitwebsitebutton.addTarget(self, action: "buttonClicked", for: UIControl.Event.touchUpInside)
Using the above, I am getting a few warning:
'self' refers to the method 'ViewController.self', which may be unexpected
Use 'ViewController.self' to silence this warning
No method declared with Objective-C selector 'buttonClicked'
Replace '"buttonClicked"' with 'Selector("buttonClicked")'
I tried to call the buttonClicked like this:
#objc func buttonClicked(sender:UIButton)
{
if(sender.tag == 5){
var abc = "argOne" //Do something for tag 5
}
print("hello")
}
And above, I am getting the below warning:
Initialization of variable 'abc' was never used; consider replacing with assignment to '_' or removing it
Replace 'var abc' with '_'
I just want to get the button to work.
This is how I solved the problem:
class ViewController: UIViewController {
private lazy var visitwebsitebutton: UIButton = {
let visitwebsitebutton = UIButton()
let mygreen = UIColor(rgb: 0x12823b)
visitwebsitebutton.backgroundColor = mygreen
visitwebsitebutton.setTitle("Visit Website", for: .normal)
visitwebsitebutton.setTitleColor(.white, for: .normal)
visitwebsitebutton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
visitwebsitebutton.layer.cornerRadius = 20
visitwebsitebutton.addTarget(self, action: #selector(visitwebsitebuttonTapped), for: .touchUpInside)
return visitwebsitebutton
}()
#objc func visitwebsitebuttonTapped() {
if let yourURL = URL(string: "https://www.somesite.com") {
UIApplication.shared.open(yourURL, options: [:], completionHandler: nil)
}
}
}
If anyone needs help with iOS mobile development with Swift, and you just want to be able to click on a button and have it take you to a site, look no further.

BulletinBoard assign gesture for ImageView

I'm using BulletinBoard (BLTNBoard) to create dialogs in my iOS app. There's an option to embed image inside it. I would like to extend it's functionality and allow user to manipulate this image using tap gesture. But eventually when I assign a gesture to it's imageView using addGestureRecognizer nothing happens.
Here's how I initiliaze bulletin and add gesture to the image:
class ViewController: UIViewController {
lazy var bulletinManager: BLTNItemManager = {
let rootItem: BLTNPageItem = BLTNPageItem(title: "")
return BLTNItemManager(rootItem: rootItem)
}()
override func viewDidLoad() {
//etc code
let bulletinManager: BLTNItemManager = {
let item = BLTNPageItem(title: "Welcome")
item.descriptionText = "Pleas welcome to my app"
item.actionButtonTitle = "Go"
item.alternativeButtonTitle = "Try to tap here"
item.requiresCloseButton = false
item.isDismissable = false
item.actionHandler = { item in
self.bulletinManager.dismissBulletin()
}
item.alternativeHandler = { item in
//do nothing by now
}
//
item.image = UIImage(named: "welcome")
//adding gesture to its imageView
item.imageView?.isUserInteractionEnabled=true
let tap = UITapGestureRecognizer(target: self, action: Selector("tapTap:"))
item.imageView?.addGestureRecognizer(tap)
return BLTNItemManager(rootItem: item)
}()
}
#objc func tapTap(gestureRecognizer: UITapGestureRecognizer) {
print("TAPTAP!!!!!!")
}
}
and nothing happens at all (no message printed in console).
However if I assign action inside alternative button it works as expected:
item.alternativeHandler = { item in
item.imageView?.isUserInteractionEnabled=true
let tap = UITapGestureRecognizer(target: self, action: Selector("tapTap:"))
item.imageView?.addGestureRecognizer(tap)
}
I guess the only thing which can prevent me to assign the tap event to it properly is that imageView becomes available much later than the bulletin is created (for example only when it is shown on the screen).
Could you please help and correct my code. Thanks
upd.
Ok, based on Philipp's answer I have the following solution:
class myPageItem: BLTNPageItem {
override func makeContentViews(with interfaceBuilder: BLTNInterfaceBuilder) -> [UIView] {
let contentViews = super.makeContentViews(with: interfaceBuilder)
let imageView=super.imageView
imageView?.isUserInteractionEnabled=true
let tap = UITapGestureRecognizer(target: self, action: #selector(tapTap))
imageView?.addGestureRecognizer(tap)
return contentViews
}
#objc func tapTap(gestureRecognizer: UITapGestureRecognizer) {
print("TAPTAP!!!!!!")
}
}
When you're working with an open source library, it's easy to check out the source code to find the answer.
As you can see here, image setter doesn't initiate the image view.
Both makeContentViews makeArrangedSubviews (which are responsible for views initializing) doesn't have any finish notification callbacks.
Usually in such cases I had to fork the repo and add functionality by myself - then I'll make a pull request if I think this functionality may be needed by someone else.
But luckily for you the BLTNPageItem is marked open, so you can just subclass it. Override makeContentViews and add your logic there, something like this:
class YourOwnPageItem: BLTNPageItem {
override func makeContentViews(with interfaceBuilder: BLTNInterfaceBuilder) -> [UIView] {
let contentViews = super.makeContentViews(with: interfaceBuilder)
// configure the imageView here
return contentViews
}
}

UIApp is nil which means we cannot dispatch control actions to their targets

I was changing file from one module to another, doing so I start getting this error in one of my tests. While earlier it was working absolutely fine.
[Assert] UIApp is nil which means we cannot dispatch control actions to their targets. If this assert is hit, we probably got here without UIApplicationMain() being executed, which likely means this code is not running in an app (perhaps a unit test being run without a host app) and will not work as expected.
In code add button in viewDidLoad()
private lazy var button: ABCTypeButton = {
let button = ABCTypeButton(title: viewModel.title, buttonType: .Payment).withAutoLayout()
button.accessibilityLabel = viewModel.title
button.accessibilityIdentifier = "paymentButton"
button.resetTintColor()
button.addTarget(self, action: #selector(ABCViewController.action1), for: .touchUpInside)
button.addTarget(self, action: #selector(ABCViewController.action2), for: .touchDown)
button.addTarget(self, action: #selector(ABCViewController.action3), for: [.touchUpOutside, .touchDragExit])
return button
}()
#objc private func action1() {
// code
}
public class ABCTypeButton: UIControl {
let iconImageView = UIImageView()
let buttonTitleLabel = UILabel()
private let chevronImageView = UIImageView(image: Icon.navigateNext.image)
private let stackView = UIStackView().withAutoLayout()
public init(title buttonTitle: String,
buttonType: FeeButtonType,
height: CGFloat = Spacing.four) {
super.init(frame: CGRect.zero)
setupViews(buttonTitle, buttonType: buttonType)
setupConstraints(height: height)
}
}
Trying to tap button from tests.
func test() {
let viewController = ViewController(viewModel: viewModel)
let button = viewController.view.findViewByIdentifier("paymentButton") as! ABCTypeButton
// I Checked that button is not nil
button.sendActions(for: .touchUpInside)
XCTAssertEqual(viewController.value, button.accessibilityIdentifier)
}
Target method action1() is not getting called
i just ran into this, and made this rough extension for the touchUpInside event. can obviously be refactored to take in whatever events you'd like to call.
extension UIButton {
public func touchUpInside(forTarget target: UIViewController) {
guard let action = actions(forTarget: target, forControlEvent: .touchUpInside)?.first else {
assertionFailure("could not find touchUpInside action for target")
return
}
target.perform(Selector(action))
}
}
I know that you asked this question 3 years ago, but maybe my answer will be helpful for somebody.
So, I did exactly what was said in the message perhaps a unit test being run without a host app.
To change this you need to go Test_Target -> General -> Host Application

cosmicmind material swift MenuView not closing

I am using cosmicmind material swift library and am following the examples code to try to get the FAB MenuView working.
I have copied the code and added the buttons i want, to test i am just testing with 2 buttons. The problem I am facing is with the handleMenu function:
/// Handle the menuView touch event.
internal func handleMenu() {
if menuView.menu.opened {
menuView.close()
(menuView.menu.views?.first as? MaterialButton)?.animate(MaterialAnimation.rotate(rotation: 0))
} else {
menuView.menu.open() { (v: UIView) in
(v as? MaterialButton)?.pulse()
}
(menuView.menu.views?.first as? MaterialButton)?.animate(MaterialAnimation.rotate(rotation: 0.125))
}
}
The full code for this UINavigationController:
import UIKit
import Material
class MyTeeUpsController: UINavigationController {
/// MenuView reference.
private lazy var menuView: MenuView = MenuView()
/// Default spacing size
let spacing: CGFloat = 16
/// Diameter for FabButtons.
let diameter: CGFloat = 56
/// Handle the menuView touch event.
internal func handleMenu() {
if menuView.menu.opened {
menuView.close()
(menuView.menu.views?.first as? MaterialButton)?.animate(MaterialAnimation.rotate(rotation: 0))
} else {
menuView.menu.open() { (v: UIView) in
(v as? MaterialButton)?.pulse()
}
(menuView.menu.views?.first as? MaterialButton)?.animate(MaterialAnimation.rotate(rotation: 0.125))
}
}
/// Handle the menuView touch event.
internal func handleButton(button: UIButton) {
print("Hit Button \(button)")
}
private func prepareMenuView() {
//let w: CGFloat = 52
var img:UIImage? = MaterialIcon.cm.add?.imageWithRenderingMode(.AlwaysTemplate)
let button1: FabButton = FabButton()//frame: CGRectMake((view.bounds.width - w)-10, 550,w,w))
button1.setImage(img, forState: .Normal)
button1.setImage(img, forState: .Highlighted)
button1.pulseColor = MaterialColor.blue.accent3
button1.backgroundColor = MaterialColor.blueGrey.lighten1
button1.borderColor = MaterialColor.blue.accent3
button1.borderWidth = 1
button1.addTarget(self, action: #selector(handleMenu), forControlEvents: .TouchUpInside)
menuView.addSubview(button1)
img = UIImage(named: "filing_cabinet")?.imageWithRenderingMode(.AlwaysTemplate)
let button2:FabButton = FabButton()
button2.depth = .None
button2.setImage(img, forState: .Normal)
button2.setImage(img, forState: .Highlighted)
button2.pulseColor = MaterialColor.blue.accent3
button2.borderColor = MaterialColor.blue.accent3
button2.borderWidth = 1
button2.backgroundColor = MaterialColor.blueGrey.lighten1
button2.addTarget(self, action: #selector(handleButton), forControlEvents: .TouchUpInside)
menuView.addSubview(button2)
menuView.menu.direction = .Up
menuView.menu.baseSize = CGSizeMake(diameter, diameter)
menuView.menu.views = [button1,button2]
view.layout(menuView).width(diameter).height(diameter).bottomRight(bottom: 58, right: 20)
}
private func prepareTabBarItem() {
//todo
}
override func viewDidLoad() {
super.viewDidLoad()
prepareMenuView()
}
}
The menu I have embedded as a subView of UINavigationController. The reason I have added to this subView is because the FAB is on top of a search/display controller (TableView) and this way the FAB can remain on top of the TableView even when scrolling the contents of the Table.
When the view initially loads, I can click on the menu button and the animation happens correctly and button2 appears. However, it does not allow me to hit the second button OR close the menu by pressing button1 again UNLESS I navigate to another tab in the tab bar controller and then navigate back to the tab where the FAB MenuView was located. I am loading my prepareMenuView() function in viewDidLoad just as it is shown in the example.
Not sure how to modify this so that it can behave as desired. It doesn't make sense to pick another ViewController lifecycle method to run prepareMenuView().
so the issue with your code is that button2 only has the selector handler for handleButton. The handleMenu handler is not added to it. So you have two solutions.
Add the handleMenu call to the handleButton
internal func handleButton(button: UIButton) {
print("Hit Button \(button)")
handleMenu(button)
}
Add a selector handler to the button2 instance for handleMenu.
button2.addTarget(self, action: #selector(handleMenu), forControlEvents: .TouchUpInside)
button2.addTarget(self, action: #selector(handleButton), forControlEvents: .TouchUpInside)
Either option will work, just remember that order matters. So if you want the menu to close before you load some content, then call the method before or add the selector handler handleMenu before you add the handleButton.
:) All the best!

UIView class for drop down list with UIButtons - delegate/protocol issue?

I am trying to create a custom drop down list in a ViewController. There are going to be 5 drop down lists and each list will have 4 options. Because of the number of lists, I decided to make a UIView that has the four choices in the form of UIButtons for each of the lists. Right now I am just trying to get one down; therefore, the following code is for ONE drop down list with FIVE options (including the one selected, which I will explain further below).
Essentially what I want is to have a button showing the selected value (or a default value at launch) and then when you click on that value then the UIView that contains 4 buttons (aka the drop down list) is shown below the original button. When the user clicks on one of the buttons I want the the button with the selected value to have the title of the button that was clicked on.
I am having the following issues:
I want to be able to pass the titles of the four buttons from the ViewController to the UIView because I want to use this UIView multiple times with different values for the titles of the four buttons. I don't know how to pass values to a UIView class.
When a choice from the drop down list (ie a UIButton) is clicked I can't figure out how to pass the value of the title of the button from the UIView back to UIViewController. I tried setting the title to a variable in the ViewController but that didn't work (showed up as nil).
Thank you so much in advance - I know this is a long questions and I am really unsure if this is even a good approach to take for what I am trying to do but it made sense in my head.
Here is my code for the ViewController
var buttonsLeft: buttonsView = buttonsView() // this is the UIView subclass
var time = UIButton.buttonWithType(UIButtonType.System) as! UIButton
override func viewWillAppear(animated: Bool) {
//hidden drop down list
self.buttonsLeft.frame = CGRect(x: self.view.bounds.width*(1/6) - 50, y:120, width:100, height: 135)
self.buttonsLeft.hidden = true
//button with selection showing or the default value at launch
self.time.frame = CGRectMake(self.view.bounds.width * (1/6) - 50, 90, 100, 30)
self.time.setTitle("1 DAY", forState: UIControlState.Normal)
self.time.addTarget(self, action: "showLeft", forControlEvents: UIControlEvents.TouchUpInside)
self.time.hidden = false
self.view.addSubview(self.time)
}
//this function shows the list
func showLeft(){
self.view.addSubview(self.buttonsLeft)
self.buttonsLeft.hidden = false
}
Here is the code for the UIView buttonsView:
import UIKit
class buttonsView: UIView {
var option1 = UIButton()
var option2 = UIButton()
var option3 = UIButton()
var option4 = UIButton()
var buttons: Array<UIButton> = Array()
var title:String = String()
override init(frame: CGRect) {
super.init(frame: frame)
self.buttons = [option1, option2, option3, option4]
self.option1.setTitle("1 DAY", forState: UIControlState.Normal)
self.option2.setTitle("1 MONTH", forState: UIControlState.Normal)
self.option3.setTitle("1 YEAR", forState: UIControlState.Normal)
self.option4.setTitle("LONGER", forState: UIControlState.Normal)
var yStep = 35
for var i:Int = 0; i < 4; ++i {
var totalY:CGFloat = CGFloat(i*yStep)
buttons[i].frame = CGRectMake(0, totalY, 100, 30)
buttons[i].addTarget(self, action: "choseOption:", forControlEvents: UIControlEvents.TouchUpInside)
buttons[i].hidden = false
self.addSubview(buttons[i])
}
}
func choseOption(sender:UIButton){
self.title = sender.titleLabel!.text!
MyView().parentTitle = sender.titleLabel!.text! // my attempt at assigning to variable in View Controller
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Delegation will help you to pass value to UIViewController.
Here are the way you can implement delegate in swift.
Step 1 : Declare protocol in class which is used to sending data. here is buttonsview.
#objc protocol MyButtonDelegate{
optional func didSelectButton(text:String)
}
Step 2 : Now declare delegate in sending class. here is buttonsview.
class buttonsView: UIView {
var delegate:MyButtonDelegate?
[other stuf......]
}
Step 3: now use delegate to send data to 'UIViewController'.
func choseOption(sender:UIButton){
delegate!.didSelectButton(text: sender.titleLabel!.text!)
}
Step 4 : adopt protocol in receiving class.
class ViewController: UIViewController,MyButtonDelegate {
Step 5: implement delegate method in receiving class.
func didSelectButton(text: String) {
parentTitle = "The Buttons title is " + text
}
Step 6: now set delegate
override func viewDidLoad() {
super.viewDidLoad()
buttonsLeft.delegate = self
}
Hope this help you.

Resources