I have the following function in swift 5 to display a message with confirm button using the SwiftMessages cocoapod. But I need to have 2 separate buttons (ex: confirm and cancel). How can I accomplish this?
public static func choiceMessage(theme: Theme = .error, title: String, message: String, buttonTitle: String = "Confirm", completion: #escaping (Bool) -> Void){
dispatchOnMain {
SwiftMessages.hideAll()
let view = MessageView.viewFromNib(layout: .messageView)
var config = SwiftMessages.Config()
view.configureTheme(theme)
view.configureContent(title: title, body: message)
view.button?.setTitle(buttonTitle, for: .normal)
config.presentationStyle = .center
config.duration = .forever
config.dimMode = .blur(style: .dark, alpha: 1, interactive: true)
view.buttonTapHandler = { _ in SwiftMessages.hide(); completion(true)}
SwiftMessages.show(config: config, view: view)
}
}
You just need to
create custom message view (your own nib file) - official docs has plenty of references
or just add and position another button dynamically
Related
I have a project that I want to add sirikit to. I added the intent and wanted to store values in my datastorage which is realm, when I tried to access the function that is used to create this task , I get an eeror. this is my code below
extension IntentHandler : INCreateTaskListIntentHandling {
public func handle(intent: INCreateTaskListIntent,
completion: #escaping (INCreateTaskListIntentResponse) -> Swift.Void) {
guard let title = intent.title else {
completion(INCreateTaskListIntentResponse(code: .failure, userActivity: nil))
return
}
CategoryFunctions.instance.createList(name: title.spokenPhrase,.....)
var tasks: [INTask] = []
if let taskTitles = intent.taskTitles {
let taskTitlesStrings = taskTitles.map {
taskTitle -> String in
return taskTitle.spokenPhrase
}
tasks = createTasks(fromTitles: taskTitlesStrings)
CategoryFunctions.instance.add(tasks: taskTitlesStrings, toList: title.spokenPhrase)
}
let response = INCreateTaskListIntentResponse(code: .success, userActivity: nil)
response.createdTaskList = INTaskList(title: title,
tasks: tasks,
groupName: nil,
createdDateComponents: nil,
modifiedDateComponents: nil,
identifier: nil)
completion(response)
}
}
this singlton instantiation works well in my app but I do not know why I get an error saying Use of unresolved identifier 'CategoryFunctions'
my CategoryFunctions singleton
class CategoryFunctions {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var database:Realm!
static let instance = CategoryFunctions()
.....
...
Select your file in xcode, on the right, choose the File Inspector, then under Target Membership, pick your Intent.
I'm abstracting the NSMenuItem using this class:
class MenuItem{
let title: String
let iconName: String
let action: Action
init(title: String, iconName: String, _ action: #escaping Action) {
self.title = title
self.iconName = iconName
self.action = action
}
#objc func doAction(sender: NSMenuItem){
action()
}
}
And here's the static function that builds the menu:
static func getMenu(items: [MenuItem]) -> NSMenu{
let m = NSMenu()
for x in items{
let item = NSMenuItem()
item.title = x.title
item.target = x // If I remove this line or the line below, there won't be any crash
item.action = #selector(MenuItem.doAction)
item.image = x.iconName.toImage()
m.addItem(item)
}
return m
}
Now my problem is whenever the contextual menu is shown, the program crashes with EXC_BAD_ACCESS error.
However, when I comment out the line that sets the target or the action, then the problem will be gone (the menu then won't be clickable of course).
So how do I fix this? Thanks.
EDIT:
I should have stated that I already tried these things:
Using #selector(x.doAction) rather than #selector(MenuItem.doAction)
Using #selector(x.doAction(sender:))
Also, there is nothing in the output window. That's why I'm seeking help here. Worse, it involves EXC_BAD_ACCESS which I can hardly grasp given that memory is supposed to be managed by the system.
So the problem is that the items inside the array I'm passing to the static getMenu function are being deallocated after the getMenu function is complete (which is very soon followed by the popUpMenuWithEvent:forView).
I solved it by having a strong reference to that array.
I'm programmatically adding a UITapGestureRecognizer to one of my views:
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))
self.imageView.addGestureRecognizer(gesture)
func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
The first problem I encountered was "Argument of '#selector' does not refer to an '#Objc' method, property, or initializer.
Cool, so I added #objc to the handleTap signature:
#objc func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
Now I'm getting the error "Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C.
It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.
It looks like you're misunderstanding a couple of things.
When using target/action, the function signature has to have a certain form…
func doSomething()
or
func doSomething(sender: Any)
or
func doSomething(sender: Any, forEvent event: UIEvent)
where…
The sender parameter is the control object sending the action message.
In your case, the sender is the UITapGestureRecognizer
Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…
func handleTap(sender: UIGestureRecognizer) {
}
you should have…
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap
Step 1: create the custom object of the sender.
step 2: add properties you want to change in that a custom object of the sender
step 3: typecast the sender in receiving function to a custom object and access those properties
For eg:
on click of the button if you want to send the string or any custom object then
step 1: create
class CustomButton : UIButton {
var name : String = ""
var customObject : Any? = nil
var customObject2 : Any? = nil
convenience init(name: String, object: Any) {
self.init()
self.name = name
self.customObject = object
}
}
step 2-a: set the custom class in the storyboard as well
step 2-b: Create IBOutlet of that button with a custom class as follows
#IBOutlet weak var btnFullRemote: CustomButton!
step 3: add properties you want to change in that a custom object of the sender
btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)
step 4: typecast the sender in receiving function to a custom object and access those properties
#objc public func btnFullRemote(_ sender: Any) {
var name : String = (sender as! CustomButton).name as? String
var customObject : customObject = (sender as! CustomButton).customObject as? customObject
var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2
}
Swift 5.0 iOS 13
I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.
Create a custom class, throw a enum to keep/make the code as maintainable as possible.
enum Vs: String {
case pulse = "pulse"
case precision = "precision"
}
class customTap: UITapGestureRecognizer {
var cutomTag: String?
}
Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.
let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true
And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.
#objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
let tag = customTag as? customTap
switch tag?.sender {
case Vs.pulse.rawValue:
// code
case Vs.precision.rawValue:
// code
default:
break
}
}
And there you have it.
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)
#objc func showAlert(_ sender: UIButton){
print("sender.tag is : \(sender.tag)")// getting tag's value
}
Just create a custom class of UITapGestureRecognizer =>
import UIKit
class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {
let userModel: OtherUserModel
init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
self.userModel = userModel
super.init(target: target, action: action)
}
}
And then create UIImageView extension =>
import UIKit
extension UIImageView {
func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
isUserInteractionEnabled = true
let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
addGestureRecognizer(gestureRecognizer)
}
#objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
}
}
Now use it like =>
self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
You can use an UIAction instead:
self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
self?.handleTap(modelObj)
}, for: .touchUpInside)
that may be a terrible practice but I simply add whatever I want to restore to
button.restorationIdentifier = urlString
and
#objc func openRelatedFact(_ sender: Any) {
if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
}
First I'll start with what I did, and in the end I will describe the problem, so it will be clearer. In general, after Android Studio, working with view elements in Xcode is still a task. I understand that a good programmer will not write the same code twice, so that each time in different Controller does not describe each time the view elements - so I wrote this function in the main Controller
class func designForButton (button: UIButton){
button.layer.masksToBounds = true
button.layer.cornerRadius = 8
}
Then you can access it in different Controller
class RegisterViewController: UIViewController {
#IBOutlet weak var buttonBack: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
ViewController.designForButton(button: buttonBack)
}
Now, what is not clear. I loaded the framework to work with Toast. And I described its values in the main Controller
func designForToast(message: String){
let style = CSToastStyle.init(defaultStyle: {
}())
_ = style?.backgroundColor = UIColor.gray
_ = style?.titleColor = UIColor.cyan
_ = style?.messageColor = UIColor.darkGray
self.view.makeToast(message, duration: 2, position: self.bottomLayoutGuide, title: "title", image: UIImage (named: "logo.jpg"), style: style ) { (success: Bool) in
}
}
But the matter is that in this case I can address to it exclusively in the same Controller
_=self.designForToast(message: "Its a Toast")
As soon as I want to make func () - like class func () to work in another Controller, Xcode starts to highlight that it's impossible, and due to my small experience, I can not fix it myself.
I suggest that you add a viewController parameter in the method so that it looks like this:
static func showToast(message: String, on viewController: UIViewController){ // I also renamed the method as well
let style = CSToastStyle.init(defaultStyle: {
}())
_ = style?.backgroundColor = UIColor.gray
_ = style?.titleColor = UIColor.cyan
_ = style?.messageColor = UIColor.darkGray
viewController.view.makeToast(message, duration: 2, position: viewController.bottomLayoutGuide, title: "title", image: UIImage (named: "logo.jpg"), style: style ) { (success: Bool) in
}
}
I also suggest that you put these "helper functions" in a designated class, like StyleHelper or something like that.
Then you can use it like this in a View Controller:
StyleHelper.showToast(message: "Hello", on: self)
Or better yet, make this an extension method!
extension UIViewController {
func showMyToast(message: String){
let style = CSToastStyle.init(defaultStyle: {
}())
_ = style?.backgroundColor = UIColor.gray
_ = style?.titleColor = UIColor.cyan
_ = style?.messageColor = UIColor.darkGray
self.view.makeToast(message, duration: 2, position: self.bottomLayoutGuide, title: "title", image: UIImage (named: "logo.jpg"), style: style ) { (success: Bool) in
}
}
}
You can then use it like this in a View Controller:
self.showMyToast(message: "Hello")
Side Note:
For views that conform to UIAppearance protocol, you can access the appearance() property to change its style globally. For example, you can do this in didFinishLaunchingWithOptions:
UIButton.appearance().tintColor = .red
And every UIButton you create will be red.
I think you need to define a protocol , and an extension to UIViewController to provide a default implementation for your method.....if this was the question...:P
protocol ToastProtocol: class {
func showToast(message:String)
}
extension ToastProtocol where Self: UIViewController {
func showToast(message:String){
///yourcode
}
}
After that you can youse your showToast method in any UIViewController
There are a couple of existing questions on this topic but they aren't quite what I'm after. I've written a little Swift app rating prompt for my app which presents two UIAlertController instances, one triggered by the other.
I'm now trying to unit test this, and trying to reach that second alert in the tests. I've written a simple spy to check the first controller, but I'd like a way to trigger one of the actions on the first alert, which in turn shows the second.
I've already tried alert.actions.first?.accessibilityActivate(), but it didn't seem to break inside the handler of that action – that's what I'm after.
A solution that doesn't involve changing the production code to allow programmatic tapping of UIAlertActions in unit tests, which I found in this SO answer.
Posting it here as well as this question popped up for me when Googling for an answer, and the following solution took me way more time to find.
Put below extension in your test target:
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
guard let block = actions[index].value(forKey: "handler") else { return }
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(actions[index])
}
}
Here's roughly what I did:
Created a mocked version of my class that would present the alert controller, and in my unit tests, used this mock.
Overrode the following method that I'd created in the non-mocked version:
func alertActionWithTitle(title: String?, style: UIAlertActionStyle, handler: Handler) -> UIAlertAction
In the overridden implementation, stored all the details about the actions in some properties (Handler is just a typealias'd () -> (UIAlertAction))
var didCreateAlert = false
var createdTitles: [String?] = []
var createdStyles: [UIAlertActionStyle?] = []
var createdHandlers: [Handler?] = []
var createdActions: [UIAlertAction?] = []
Then, when running my tests, to traverse the path through the alerts, I implemented a callHandlerAtIndex method to iterate through my handlers and execute the right one.
This means that my tests look something like this:
feedback.start()
feedback.callHandlerAtIndex(1) // First alert, second action
feedback.callHandlerAtIndex(2) // Second alert, third action
XCTAssertTrue(mockMailer.didCallMail)
I took a slightly different approach based on a tactic I took for testing UIContextualAction—it's very similar to UIAction but exposes its handler as a property (not sure why Apple wouldn't have done the same for UIAction). I injected an alert actions provider (encapsulated by a protocol) into my view controller. In production code, the former just vends the actions. In unit tests, I use a subclass of this provider which stores the action and the handler in two dictionaries—these can be queried and then triggered in tests.
typealias UIAlertActionHandler = (UIAlertAction) -> Void
protocol UIAlertActionProviderType {
func makeAlertAction(type: UIAlertActionProvider.ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction
}
Concrete object (has typed titles for easy retrieval later):
class UIAlertActionProvider: UIAlertActionProviderType {
enum ActionTitle: String {
case proceed = "Proceed"
case cancel = "Cancel"
}
func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
let style: UIAlertAction.Style
switch title {
case .proceed: style = .destructive
case .cancel: style = .cancel
}
return UIAlertAction(title: title.rawValue, style: style, handler: handler)
}
}
Unit testing subclass (stores actions and handlers keyed by ActionTitle enum):
class MockUIAlertActionProvider: UIAlertActionProvider {
var handlers: [ActionTitle: UIAlertActionHandler] = [:]
var actions: [ActionTitle: UIAlertAction] = [:]
override func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
handlers[title] = handler
let action = super.makeAlertAction(title: title, handler: handler)
actions[title] = action
return action
}
}
Extension on UIAlertAction to enable typed action title lookup in tests:
extension UIAlertAction {
var typedTitle: UIAlertActionProvider.ActionTitle? {
guard let title = title else { return nil }
return UIAlertActionProvider.ActionTitle(rawValue: title)
}
}
Sample test demonstrating usage:
func testDeleteHandlerActionSideEffectTakesPlace() throws {
let alertActionProvider = MockUIAlertActionProvider()
let sut = MyViewController(alertActionProvider: alertActionProvider)
// Do whatever you need to do to get alert presented, then retrieve action and handler
let action = try XCTUnwrap(alertActionProvider.actions[.proceed])
let handler = try XCTUnwrap(alertActionProvider.handlers[.proceed])
handler(action)
// Assert whatever side effects are triggered in your code by triggering handler
}
I used Luke's guidance above to create a subclass of UIAlertAction that saves its completion block so it can be called during tests:
class BSAlertAction: UIAlertAction {
var completionHandler: ((UIAlertAction) -> Swift.Void)?
class func handlerSavingAlertAction(title: String?,
style: UIAlertActionStyle,
completionHandler: #escaping ((UIAlertAction) -> Swift.Void)) -> BSAlertAction {
let alertAction = self.init(title: title, style: style, handler: completionHandler)
alertAction.completionHandler = completionHandler
return alertAction
}
}
You could customize this to save more information (like the title and the style) if you like. Here's an example of an XCTest that then uses this implementation:
func testThatMyMethodGetsCalled() {
if let alert = self.viewController?.presentedViewController as? UIAlertController,
let action = alert.actions[0] as? BSAlertAction,
let handler = action.completionHandler {
handler(action)
let calledMyMethod = self.presenter?.callTrace.contains(.myMethod) ?? false
XCTAssertTrue(calledMyMethod)
} else {
XCTFail("Got wrong kind of alert when verifying that my method got called“)
}
}