Swift - open URL from button click - ios

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.

Related

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

Trigger action from UIButton click on custom XIB

I am building a quote app using TGLParallaxCarousel library in my project. I try to custom the CustomView of TGLParallaxCarouselItem by adding two UIButtons (favButton and shareButton) on it.
screenshot to the quote cards (CustomView) I create
I am able to change the UIButton view based on its state--whether the current quote is faved or not, by doing this:
convenience init(frame: CGRect, number: Int) {
self.init(frame: frame)
currentQuote = quoteData[number]
favButton.tag = number
currentQuote.faved == true ? favButton.setImage(#imageLiteral(resourceName: "fav-on"), for: .normal) : favButton.setImage(#imageLiteral(resourceName: "fav-off"), for: .normal)
}
However I need to be able to turn the fav on and off by clicking the favButton. I tried to connect the favButton directly as an IBAction to the XIB file, tried to addAction to function, but I still can't access the favButton click state.
Please help. What should I do?
UPDATE
I've tried addTarget on favButton. It's not working. My tap is detected as tap on CustomView rather than specifically on favButton.
Here's the detectTap function that fired when I tap anywhere on the CustomView (including on the favButton). This function is within the TGLParallaxCarousel.swift
func detectTap(_ recognizer:UITapGestureRecognizer) {
let targetPoint: CGPoint = recognizer.location(in: recognizer.view)
currentTargetLayer = mainView.layer.hitTest(targetPoint)!
guard let targetItem = findItemOnScreen() else { return }
let firstItemOffset = (items.first?.xDisp ?? 0) - targetItem.xDisp
let tappedIndex = -Int(round(firstItemOffset / xDisplacement))
self.delegate?.carouselView(self, didSelectItemAtIndex: tappedIndex)
if targetItem.xDisp == 0 {
self.delegate?.carouselView(self, didSelectItemAtIndex: tappedIndex)
}
else {
selectedIndex = tappedIndex
}
}
Did you try to use addTarget?
convenience init(frame: CGRect, number: Int) {
self.init(frame: frame)
currentQuote = quoteData[number]
favButton.tag = number
currentQuote.faved == true ? favButton.setImage(#imageLiteral(resourceName: "fav-on"), for: .normal) : favButton.setImage(#imageLiteral(resourceName: "fav-off"), for: .normal)
favButton.addTarget(self, action: #selector(toggle), for: .touchUpInside)
}
#objc fileprivate func toggle() {
currentQuote.faved = !currentQuote.faved
currentQuote.faved == true ? favButton.setImage(#imageLiteral(resourceName: "fav-on"), for: .normal) : favButton.setImage(#imageLiteral(resourceName: "fav-off"), for: .normal)
}

Assign 'action' dynamically in uibutton.addTarget

please bear with me, as I'm new to swift -4 weeks old-.
I've created the following 2 functions in fileA.swift
func custombttn(theSelector:Selector)-> UIButton{
let bttn = UIButton(frame: CGRect(x:20, y:400, width:200, height:30))
bttn.setTitle("tap this button", for: UIControlState.normal)
bttn.backgroundColor = UIColor.black
bttn.setTitleColor(UIColor.magenta, for: UIControlState.normal)
bttn.addTarget(bttn, action: theSelector, for: UIControlEvents.touchUpInside)
return bttn
}
func customtxtfld() -> UITextField{
let txtField = UITextField(frame: CGRect(x:20, y:360, width:200, height:30))
txtField.borderStyle = UITextBorderStyle.roundedRect
txtField.backgroundColor = UIColor.magenta
txtField.placeholder = "Do you like me now..?"
return txtField
}
The reason behind the custombttn(theSelector:Selector), is that i want to pass the function dynamically to the button in my viewcontroller file.
Now, moving the fileB.swift, I have the following code...
class TabOneViewController: UIViewController{
let txt = customtxtfld()
let bttn = custombttn(theSelector: #selector(updatetxt))
override func loadView() {
super.loadView()
view.addSubview(txt)
view.addSubview(bttn)
}
func updatetxt(){
txt.text = "hello, you!"
}
}
Here is where things get tricky, when I attempt to build, I don't get any error (not even a warning). However, when I run the app, and tap the bttn in fileB.swift, I get the following error during runtime:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[UIButton updatetxt]:
unrecognized selector sent to instance 0x7f8453415670'
If I have 2 or more functions in my fileB.swift that I wish to assign dynamically to the action part of the addTarget, is there any way I can pass the selector dynamically to a button..?
Appreciate your time and assistance. Please let me know if I need to explain something further.
It's crashing because your button target is wrong.
func custombttn(target:Any, theSelector:Selector)-> UIButton{
let bttn = UIButton(frame: CGRect(x:20, y:400, width:200, height:30))
bttn.setTitle("tap this button", for: UIControlState.normal)
bttn.backgroundColor = UIColor.black
bttn.setTitleColor(UIColor.magenta, for: UIControlState.normal)
bttn.addTarget(target, action: theSelector, for: UIControlEvents.touchUpInside)
return bttn
}
And use it like this
class TabOneViewController: UIViewController{
let txt = customtxtfld()
override func loadView() {
super.loadView()
view.addSubview(txt)
let bttn = custombttn(target:self, theSelector: #selector(updatetxt))
view.addSubview(bttn)
}
func updatetxt(){
txt.text = "hello, you!"
}
}
Yes, you can. The issue here is that you passed the button itself as the target for the action. Just pass the correct target when adding the action, which in this case is the instance of your view controller.

Passing closure in swift as parameter to be used by selector in function

I am trying to create a generic button creation function into which I pass a closure that represents the action that results as a result of clicking on the button. My code is below. However, I get the following error:
Argument of #selector cannot refer to property. Any suggestions for a workaround ? I don't want to write separate functions for which everything else is the same except for the target action.
func myButton(textColor tColor:UIColor , title:String,
_ buttonFcn: (UIButton) -> Void,
titleSize:CGFloat=30) -> UIButton {
let newButton = UIButton(type: .System)
let bgColor = UIColor(red:204/255, green:204/255, blue:204/255, alpha:1.0)
newButton.backgroundColor = bgColor
newButton.setTitle(title, forState: .Normal)
newButton.setTitleColor(tColor, forState: .Normal)
newButton.titleLabel?.font = newButton.titleLabel?.font.fontWithSize(titleSize)
newButton.addTarget(self, action:#selector(buttonFcn),
forControlEvents:
UIControlEvents.TouchUpInside)
return newButton
}
The problem is that the target-action mechanism is an Objective-C mechanism, and therefore is predicated on the notion that the action selector is a method of an object. You need, therefore, to have some NSObject-based object that has this function as a method, and which can then serve as the target.
Thus, if what differs in every case is the target and the action, what you need to pass is a reference to the target along with the selector string. Swift will squawk at this, but if you know how to form a selector string correctly you can certainly get away with it; you just won't be able to use the #selector syntax, and so you will risk crashing if you form the selector string incorrectly. But it's the kind of thing we used to do all the time in the old Objective-C days, so go right ahead if that's your aim.
Totally artificial but working example:
func buttonMaker(target:NSObject, selectorString:String) -> UIButton {
let b = UIButton(type:.system)
b.setTitle("Testing", for: .normal)
b.addTarget(target, action: Selector(selectorString), for: .touchUpInside)
b.sizeToFit()
return b
}
And here's how to call it from a view controller:
func doButton(_ sender:Any) {
print("ha!")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let b = buttonMaker(target:self, selectorString:"doButton:")
b.frame.origin = CGPoint(x:100, y:100)
self.view.addSubview(b)
}
And when we tap the button, we don't crash (rather, we print "ha"), because I know how to make selector strings correctly. But, as you can see, to accomplish this I had to give up the use of #selector altogether, so safety is out the window. If I had written my selector string incorrectly — for instance, if I had spelled it wrong, or omitted the colon — we'd have crashed on the button tap, just like we used to all the time before Swift #selector and Objective-C #selector were invented.
If your deployment target is iOS 14 or later, you can use the addAction method instead of addTarget. The addAction method lets you use a closure instead of a selector:
func myButton(
textColor: UIColor,
title: String,
titleSize: CGFloat = 30,
_ handler: #escaping (UIButton) -> Void
) -> UIButton {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(red: 204/255, green: 204/255, blue: 204/255, alpha: 1.0)
button.setTitle(title, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.titleLabel?.font = button.titleLabel?.font.withSize(titleSize)
let action = UIAction { action in
guard let button = action.sender as? UIButton else { return }
handler(button)
}
button.addAction(action, for: .touchUpInside)
return button
}
iOS 14 was released on 2020-09-16 and supports iPhone 6S and later devices.

Adding UIButton dynamically: action not getting registered

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?

Resources