Execute code when pressing button created programmatically in function - swift

I have created a custom Alert View with buttons that are created dynamically.
Everything works fine except I don't know how to make each button do something different.
What I mean by that is that I call a function addButton and I pass a block of code (like in UIAlertAction()). I want that code to execute after I press the button but it actually gets executed when the button is loaded.
Sorry if I'm unclear but I don't know the specific terms about my issue. Basically I tried to reproduce UIAlertView and I don't know how to fully remake UIAlertAction.
Here's the method (note: it's in a custom class):
func addButton(buttonNumber number: Int, title: String, color: UIColor, actions: () -> Void ) {
// Initialization
let button = UIButton(frame: CGRectMake(5, self.wrapper.bounds.height-CGFloat(55*number), self.wrapper.bounds.width-10, 50))
// Configuration
button.setTitle(title, forState: .Normal)
button.backgroundColor = color
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(myCustomView.buttonPressed(actions:)), forControlEvents: .TouchUpInside)
wrapper.addSubview(button)
}
This is the selector (I know it's not correct):
func buttonPressed(actions actions: () -> Void) {
actions() // How do I get the actions from addButton?
}
And this is when I call addButton:
let alertView = UIStoryboard(name: "Popups", bundle: nil).instantiateViewControllerWithIdentifier("HAlertView") as! HAlertViewVC
prepareAlert(alertView)
alertView.loadAlert(title: "Hold on", message: "You need to add at least the goal before saving", image: "Warning-Icon", numberOfActions: 1)
alertView.addButton(buttonNumber: 1, title: "Close", color: UIColor(red: 246.0/255.0, green: 71.0/255.0, blue: 71.0/255.0, alpha: 1), actions: {
// alertView.dismiss() // This is the problem. That's just a function that hides the alert view but the issue is that it gets executed right away.
})

What you are looking for are closures:
var some_code_block = {
print("my awesome code block!")
}
Put this at a higher / global scope, then you can run / change the block at will, simply by adding '()' at the end of the closure (variable name)
func addCode() {
// Wont execute:
some_code_block = { print("new code inserted") }
}
func buttonPressed() {
// Executes:
some_code_block()
}
Personally, I use arrays / dictionaries to store / update code blocks on the fly.. then you can iterate through / match indices/keys to get the code block you want.
https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
If your problem is actually making different buttons, then you can store your newly created instance of a UI button into an array or dictionary
[UIButton]
An array / dictionary of buttons, matching with an array / dictionary of code blocks can certainly work... You just have to make sure they are in the right scope / and synchronize indices / keys.
Or, you could create a class / struct that holds a UIButton instance, and a code block, then put the class / struct into an array / dictionary
// Make your own initializer for your purposes
struct ButtonStuff {
var button: UIButton?
var code_block: ()?
}
// Make empty array that holds type ButtonStuff (global / outer scope)
var buttons: [ButtonStuff] = []
func addButton(pram1:Pram1, pram2:Pram2) {
// Make a new button (called locally inside of a func)
var new_button = ButtonStuff()
// Put stuff in here...
// new_button.button = pram1
// new_button.code_block = pram2
// Add our locally made new_button to the Global buttons array
buttons.append(new_buttons)
}
NOTE: The pram1, pram2 can be whatever you want, but to store to the code block (without running it) you want type (). If you want to automatically run the code block, you would use type ()->()
There are many ways of doing the above (you don't have to use optionals); just a quick example. You would then just have to make some logic / algo to find the right button, display it, then call the code block.
If you have any questions, need me to edit my answer, please leave a message in the comments with #fluidity

Related

Programmatically Adding/Removing Images to Subview in Swift

Using code found in another post on here, I was able to programmatically draw and erase a subview, including location, width, and height, as shown in func addLoadButton and func removeSubview below. I have also figured out how to programmatically draw a picture on the main View Controller, as shown in func trailerLoadImage below. However, after many hours and attempts, I have tried to programmatically add and remove images into that subview without success.
My end goal is to be able to press three different trailer load type buttons to insert three different images (button 1 loads image 1, button 2 loads image 2, etc.) in a subview located in a specific location on the screen, and to be able to remove the images one at a time (may not be in order put on screen) by tapping on the images with a finger. The subview can be permanent or can be created and removed programmatically (as used below).
What code would I use to insert an image or multiple different images into a subview that has already been created, to remove the image(s) in the reverse order added, and to clear all images out of the subview? If this can’t be done, an acceptable alternative would be the ability to remove the image from the main VC by either tapping on it or pressing a button to clear all added images.
//Class declaration
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
//Boolean to include load type one in calculations
var trailerLoad : Bool = false
var trailerLoadDistanceFromFront = 20
//Boolean to include load type two in calculations
var trailerLoadTwo : Bool = false
var trailerLoadTwoDistanceFromFront = 80
//Boolean to include load type three in calculations
var trailerLoadThree : Bool = false
var trailerLoadThreeDistanceFromFront = 120
var trailerLoadWidth : Int = 0
var trailerLoadX : Int = 0
//Boolean true only when subView on trailer is active
var subViewActive : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
//Picker view data sources and delegates included in here and work fine
}
//Adds subview for loads
#IBAction func addLoadButton(_ sender: Any) {
let trailerLoadView: UIView = UIView(frame: CGRect(x: 252, y: 233, width: 378, height: 100))
trailerLoadView.backgroundColor = .blue
trailerLoadView.alpha = 0.5
trailerLoadView.tag = 100
trailerLoadView.isUserInteractionEnabled = true
self.view.addSubview(trailerLoadView)
subViewActive = true
}
//If subViewActive is true, calls alert to get distance load type one is from front, moves on to insert and position image, changes trailerLoad bool to true
#IBAction func trailerLoadOneButton(_ sender: Any) {
//If subViewActive is true:
//Calls alert to get distance load type one is from front, puts in var trailerLoadDistanceFromFront
//Calls trailerLoadImage() to insert and position load type one image
//Changes bool trailerLoad to true
//If subViewActive is false:
//Calls alert to tell user that they need to click Add Load button (create subview) before adding load types one, two, or three
}
//Add trailer load type one image, scales and positions it relatively accurately in view.
//To be duplicated and modified for load types two and three in the future, with different images (trailerLoadTypeTwoPic and trailerLoadTypeThreePic)
func trailerLoadImage() {
trailerLoadWidth = 378 * 60 / trailerTotalLength
trailerLoadX = 378 * trailerLoadDistanceFromFront / trailerTotalLength
let imageView = UIImageView(frame: CGRect(x: (252 + trailerLoadX), y: (333 - trailerLoadWidth), width: trailerLoadWidth, height: trailerLoadWidth));
let image = UIImage(named: “trailerLoadTypeOnePic”);
imageView.image = image;
self.view.addSubview(imageView)
}
//Calls func removeSubview to remove subview
#IBAction func resetButton(_ sender: Any) {
removeSubview()
}
//Removes subview for loads
#objc func removeSubview(){
subViewActive = false
if let viewWithTag = self.view.viewWithTag(100) {
viewWithTag.removeFromSuperview()
}else{
print("No!")
}
}
}
Thank you very much to anybody that offers assistance or advice.
Don't use tags! Just create variables in global scope for your views
var imageViews = [UIImageView]()
then when you need to add them first append them to your array and then add them to view
imageViews.append(imageView)
view.addSubview(imageView)
Then when you need to remove your all views from their superview, use method removeFromSuperview() for each view in array
imageViews.forEach { $0.removeFromSuperview() }
imageViews.removeAll()
or if you need to remove just one view at specific index
imageViews[index].removeFromSuperview()
imageViews.remove(at: index)

How can I UITest the change of the image of a UIButton in Swift?

When I tap a UIButton, the image should change to reflect its new state (e.g. Record -> Pause etc).
In my XCode UITest function, how do I interrogate the buttons current image after the tap to assert that its image has changed correctly to the correct image's .png file?
I did it like this
// Find the button by the image name
// In this example the image's names are "record_image" and "pause_image"
// "_" are replaced by " " in the image name when searching
let recordButton = XCUIApplication().buttons["record image"]
recordButton.tap()
XCTAssertFalse(recordButton.exists) // Record button won't exist anymore since the image has changed
let pauseButton = XCUIApplication().buttons["pause image"]
XCTAssertTrue(pauseButton.exists)
pauseButton.tap()
XCTAssertFalse(pauseButton.exists)
XCTAssertTrue(recordButton.exists)
I did like this, not best way but efficient for me,
every image change I changed accessibility identifier then checked the access.ids
public func setFollowed(_ isFollowed: Bool) {
if isFollowed {
followButton.setImage(UIImage(named: "followed-green-icon"), for: .normal)
followButton.accessibilityIdentifier = "ProfileInfoView_followButton_followed"
}
else {
followButton.setImage(UIImage(named: "follow-blue-icon"), for: .normal)
followButton.accessibilityIdentifier = "ProfileInfoView_followButton_follow"
}
}
sample UI test part:
func getFollowButton(isFollowed: Bool) -> XCUIElement {
if isFollowed == true {
return app.descendants(matching: .button)["ProfileInfoView_followButton_followed"]
} else {
return app.descendants(matching: .button)["ProfileInfoView_followButton_follow"]
}
}
then tested returned element, state changed etc.

WatchKit 2 Complication Text Only Shows Up in Preview

I'm trying to develop a very simple complication for watchkit2 that says "Hi" from a simple text provider.
I've managed to achieve some strange behavior; I can see the text when the complication is clicked or when you are previewing it from the customize watchface screen, but not when the watchface is displayed. Have a look:
Any ideas what might be causing this?
My text provider looks like this
var textProvider: CLKSimpleTextProvider
override init() {
textProvider = CLKSimpleTextProvider()
textProvider.text = "Hi"
textProvider.shortText = "HI"
textProvider.tintColor = UIColor.whiteColor()
super.init()
}
And my get getPlaceholderTemplateForComplication looks like
func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
switch complication.family {
case .ModularSmall:
let stemplate = CLKComplicationTemplateModularSmallSimpleText()
stemplate.tintColor = UIColor.whiteColor()
stemplate.textProvider = textProvider
handler(stemplate)
case .CircularSmall:
let stemplate = CLKComplicationTemplateCircularSmallSimpleText()
stemplate.tintColor = UIColor.whiteColor()
stemplate.textProvider = textProvider
handler(stemplate)
default:
handler(nil)
}
}
While customizing watch face, Apple Watches calls getPlaceholderTemplateForComplication:withHandler: to show placeholder text. Since you've implemented it - you can see "Hi". That is cool.
But when watch face displayed, it calls another methods, such as:
getCurrentTimelineEntryForComplication:withHandler:
getTimelineEntriesForComplication:beforeDate:limit:withHandler:
getTimelineEntriesForComplication:afterDate:limit:withHandler:
And it seems like you're not implemented them. So implementing these method will resolve your issue.
You can find more detailed information about these methods in this WWDC 2015 Tutorial: https://developer.apple.com/videos/wwdc/2015/?id=209

Programmatically place the Cursor inside a textField of a custom tableView header

How can you programmatically make sure that the cursor of a tableView-HeaderView-TextField gets active (i.e. is the first responder) ??
My table looks like this (i.e with the custom TextField header). So far, the cursor only gets inside the grey header field by clicking inside the textfield. But I would like to be able to get the cursor inside the textfield programmatically....
The code for my custom tableview-header looks like this :
// drawing a custom Header-View with a TextField on top of the tableView
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let container = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 50))
let textField = UITextField(frame: CGRectMake(10, 15, self.view.frame.size.width/2 - 40, 45))
textField.delegate = self
self.txtfield = textField
textField.textColor = UIColor.blackColor()
let placeholder = NSAttributedString(string: "..add player", attributes: [NSForegroundColorAttributeName: UIColor.darkGrayColor()])
textField.attributedPlaceholder = placeholder
textField.backgroundColor = UIColor.lightGrayColor()
container.addSubview(textField)
var headPlusBttn:UIButton = UIButton.buttonWithType(UIButtonType.ContactAdd) as! UIButton
headPlusBttn.center.x = self.view.frame.size.width - 20
headPlusBttn.center.y = 38
headPlusBttn.enabled = true
headPlusBttn.addTarget(self, action: "addTeam:", forControlEvents: UIControlEvents.TouchUpInside)
container.addSubview(headPlusBttn)
return container
}
My first approach was to set the first-responder of the headerViewForSection like this (see code):
// reload entries
func reloadEntries() {
self.tableView.reloadData()
// the following does unfortunately not work !!!!!
self.tableView.headerViewForSection(1)?.becomeFirstResponder()
}
Not sure why this does not work. Maybe, the Section-Nr (Int=1) is wrong. But I tried several section-numbers. No curser where it should be.
Any help appreciated !
Usually adding a delay helps in situations like this. It allows the OS to do everything it wants with the view, and then it won't mess up what you're trying to do at the same time.
Maybe something like this:
func reloadEntries() {
self.tableView.reloadData()
let delay = (Int64(NSEC_PER_SEC) * 0.1)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, triggerTime), dispatch_get_main_queue(), { () -> Void in
self.tableView.headerViewForSection(1)?.becomeFirstResponder()
})
}
I haven't tested this to do what you want, so you may need to find a different place to put this.
Also, are you sure you want to affect the view in section 1? From your image it looks like you want to mess with the header in section 0.
Be sure to drop into the debugger and check that the header isn't nil. Your code implies that that's a valid condition. You might try writing it like this (at least for testing):
if let header = self.tableView.headerViewForSection(1) {
header.becomeFirstResponder()
}
else {
print("There is no header.")
}
Try
self.tableView.headerViewForSection(1)?.textfield.becomeFirstResponder()

Swift generic function not working

So, I made a generic function in a structure which has some static methods for helping to create customized UIButtons and so on. So I did this code:
static func createAlertPicker<T: UIViewController where T: UIPickerViewDelegate, T: UIPickerViewDataSource>(#title: String, inout forPicker picker: UIPickerView, viewController: T) -> UIAlertController {
let alert = UIAlertController(title: title, message: "\n\n\n\n\n\n\n\n\n\n", preferredStyle: .Alert)
alert.view.tintColor = data.backgroundColor
picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return alert
}
I don't get an error doing this but when calling this method in a ViewController like so:
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &self.pickerView!, viewController: self)
I get a crazy error telling me Type 'UIViewController' does not conform to protocol 'UIPickerViewDelegate'. Although it is implemented, that's the ViewController's declaration:
class PlayerDetails:UIViewController, UITableViewDataSource, UITableViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource
By the way, this problem only occurs in one ViewController, I'm calling it several times. Maybe it should be mentioned that this line of code (let alert = ...) is not even compiled in the first place.
I really don't understand this. Thank for any help ! :]
Yeah that error is a total lie.
The problem is with the middle parameter: &self.pickerView!.
self.pickerView is an optional. Your optional contains a reference, but unwrapping that reference passes back you back a fresh copy of the reference by value. You don’t get access to the original reference inside the optional. So when you call !, you get an immutable value. You can’t change it or assign to it, and that means you can’t pass it as an inout parameter.
Here’s a simpler example:
let i: Int? = 5
func f(inout i: Int) { i = 6 }
f(&i!) // error: 'Int' is not convertible to '#lvalue inout $T3’
This is the compiler saving you from a potentially very confusing runtime bug – if the value were passed in and changed, it would make no difference to the value you actually intended to change. Only the temporary copy would have been changed.
It might be a bit confusing because classes are reference types so you’re not used to thinking about them in value terms. But references themselves are values. What you are getting out of the unwrap is a copy of the reference, not a copy of the thing referred to.
If you change your call to something like the following, it should work:
if var picker = self.pickerView {
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &picker, viewController: self)
// don’t forget to assign the value back...
self.pickerView = picker
}
This version also has the benefit of not exploding in flames if you’ve ever forgotten to set forPicker to be a value before you force unwrap it.
But if all you are using the inout for is to return a new picker (doesn’t look in your createAlertPicker like you use the value passed in, only assign to it), then why not ditch the inout and make the function return a pair of values:
static func createAlertPicker
<T: UIViewController where T: UIPickerViewDelegate>
(#title: String, viewController: T)
// return a tuple
-> (UIPickerView,UIAlertController) {
// etc…
var picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return (picker, alert)
}
let (picker, alert) = CreatorClass.createAlertPicker(title: "select sortage”, viewController: self)
self.pickerView = picker