I wanted to create a keyboard selection from #IBInspectabale
How to achieve this
I am creating a view, In which I insert an ImageView and a TextField,
Now I am creating this custom view class as #IBDesignable and created the #IBInspectable elements.
I successfully create side image and placeholder elements but now I am trying to create the keyboard type but facing issues.
code snipped : `import UIKit
#IBDesignable
class CustomTextField: UIView,UITextFieldDelegate {
//custom view from the XIB file
var view: UIView!
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib ()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib ()
}
func loadViewFromNib() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "CustomTextField", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view);
}
#IBInspectable var sideImage: UIImage? {
get {
return imageView.image
}
set(sideImage) {
imageView.image = sideImage
}
}
#IBInspectable var placeHolderText: String? {
get {
return textField.placeholder
}
set(placeHolderText) {
textField.placeholder = placeHolderText
}
}'
all the above working fine,
but following is not working for me:
#IBInspectable var keyboard: UIKeyboardType? {
get{
return UIKeyboardType(rawValue: textField.keyboardType.rawValue)
}
set(keyboard){
textField.keyboardType = keyboard!
}
}
}
I tried it by creating enum but it does not give any result for me.
First of all thanks to everyone.
My problem is solved without doing any extra effort of creating enum and all.
I used the apple predefined UIKeyboardType enum.
Just write the following code:
#IBInspectable var keyboard:Int{
get{
return self.textField.keyboardType.rawValue
}
set(keyboardIndex){
self.textField.keyboardType = UIKeyboardType.init(rawValue: keyboardIndex)!
}
}
And it will show the Keyboard in Interface builder, and you can set 0,1,2... value for your keyboard type.
where the 0,1,2 represent as follows:
0: default // Default type for the current input method.
1: asciiCapable // Displays a keyboard which can enter ASCII characters
2: numbersAndPunctuation // Numbers and assorted punctuation.
3: URL // A type optimized for URL entry (shows . / .com prominently).
4: numberPad // A number pad with locale-appropriate digits (0-9, ۰-۹, ०-९, etc.). Suitable for PIN entry.
5: phonePad // A phone pad (1-9, *, 0, #, with letters under the numbers).
6: namePhonePad // A type optimized for entering a person's name or phone number.
7: emailAddress // A type optimized for multiple email address entry (shows space # . prominently).
8: decimalPad // A number pad with a decimal point.
9: twitter // A type optimized for twitter text entry (easy access to # #)
It is not possible to use enum types for #IBInspectable vars. You have to set your var as a String or Int.
From Apple's docs:
You can attach the IBInspectable attribute to any property in a class
declaration, class extension, or category for any type that’s
supported by the Interface Builder defined runtime attributes:
boolean, integer or floating point number, string, localized string,
rectangle, point, size, color, range, and nil.
Related
I have a custom UIView class that is initiated from the xib file. It has instance property called title of type String?. Whenever, the title property is set, the text of a UITextField gets changed to the value of the title property.
If the title property is a stored property, the program works as expected.
If the title property is a computed property, then the program crashes with EXC_BAD_ACCESS error which I assume is because an IBOutlet had not yet been initialized.
Can anyone explain why if title is a stored property, it works but if it is an computed property it fails?
Following is the source code-
The NibView is a subclass of UIView and handles the loading of xib file
class NibView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadNib()
}
}
The implementation of loadNib method is inside an extension
extension UIView {
func loadNib() {
guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }
view.frame = bounds
addSubview(view)
}
}
The definition of nib property on UIView is in another extension
extension UIView {
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
var nib: UINib {
return type(of: self).nib
}
}
The following class is the class which has the title property.
class ProgressView: NibView {
var title: String? {
didSet {
titleLabel.text = title
}
}
#IBOutlet private weak var titleLabel: UILabel!
}
The above class is used as follows-
let view = ProgressView()
addSubview(view)
view.title = "Loading"
Running the above code works as expected.
However if the implementation of ProgressView is changed to use a computed property as below, then it fails
class ProgressView: NibView {
var title: String? {
get {
return titleLabel.text
}
set {
titleLabel.text = newValue
}
}
#IBOutlet private weak var titleLabel: UILabel!
}
Can anyone point out why where is difference in behaviour when the title property is computed instead of being stored?
Edit -
The main thread crashes with "Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)"
The method on top of the call stack is "ProgressView.title.modify".
Edit 2-
I am not sure what I have done but I am unable to reproduce the issue after restarting xcode. Even if computed property is used, it works as expected.
Your description is far from explanatory, but I'm guessing that there is a ProgressView nib in which the File's Owner is a ProgressView, and there is an titleLabel outlet from the File's owner to a label inside the nib. (I assume this because otherwise I can't explain your use of withOwner: self.)
On that assumption I can't reproduce any problem. Both your ways of expressing title work just fine for me. I put print statements to make sure the right one was being called, and it is; no matter whether this is a didSet or the setter of a computed property, we load just fine and I see the "Loading" text.
My code is in a view controller's viewDidLoad, if that makes a difference.
(By the way, I regard your use of ProgressView() with suspicion. This results in a zero-size view. It might not seem to make any difference, but it's a bad idea. The label is a subview of the zero-size view. If the zero-size view clipped its subviews, the label would be invisible. Even if the zero-size view does not clip its subviews, if the label were a button, the button would not work. Zero-size views are a bad idea. You should give your ProgressView a real frame.)
When selecting a native switch with VoiceOver, the announcement will contain "Off" or "On" with an additional hint "double tap to toggle setting".
I have tried using the accessibility trait UIAccessibilityTraitSelected, but that only results in "Selected" being announced, with no hint unless I provide one explicitly.
Using the Accessibility Inspector I've also noticed that native UIKit switches have an accessibilityValue of 1 when enabled, but providing that does not change VoiceOver behavior.
- (UIAccessibilityTraits)accessibilityTraits {
if (toggled) {
return UIAccessibilityTraitSelected;
} else {
return UIAccessibilityTraitNone;
}
}
- (NSString*)accessibilityValue {
if (toggled) {
return #"1";
} else {
return #"0"
}
}
Is it possible to provide some combination of traits/value/label such that TalkBack recognizes this element as a Switch, without using a UISwitch?
I have created an accessible view that acts like a switch here.
The only way that I have been able to get any arbitrary element to act like a Switch is when inheriting the UIAccessibilityTraits of a Switch. This causes VoiceOver to read the Accessibility Value (0 or 1) as "Off" or "On," adds the hint "Double tap to toggle setting", and makes VoiceOver say "Switch Button."
You could potentially do this by overriding the view's Accessibility Traits like so:
override var accessibilityTraits(): UIAccessibilityTraits {
get { return UISwitch().accessibilityTraits }
set {}
}
Hope this helps!
You can create a custom accessibility element behaving like a UISwitchControl with whatever you want.
The only thing to be specified is the way VoiceOver should interpret it.
Let's suppose you want to gather a label and a view to be seen as a switch control.
First of all, create a class for grouping these elements into a single one :
class WrapView: UIView {
static let defaultValue = "on"
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(with label: UILabel,and view: UIView) {
let viewFrame = label.frame.union(view.frame)
self.init(frame: viewFrame)
self.isAccessibilityElement = true
self.accessibilityLabel = label.accessibilityLabel
self.accessibilityValue = WrapView.defaultValue
self.accessibilityHint = "element is" + self.accessibilityValue! + ", tap twice to change the status."
}
}
Then, just create your custom view in your viewDidAppear() :
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
#IBOutlet weak var myLabel: UILabel!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let myCustomView = WrapView.init(with: myLabel, and: myView)
self.view.addSubview(myCustomView)
}
}
Finally, to have a custom view behaving like a switch control, just override the accessibilityActivate function in your WrapView class to implement your logic when your view is double tapped :
override func accessibilityActivate() -> Bool {
self.accessibilityValue = (self.accessibilityValue == WrapView.defaultValue) ? "off" : "on"
self.accessibilityHint = "element is" + self.accessibilityValue! + ", tap twice to change the status."
return true
}
And now you have a custom element that contains whatever you want and that behaves like a switch control for blind people using VoiceOver without using a UISwitch as you wanted.
the problem I am having is that I have reusable views / controls that contain text fields. These are xib files with a custom UI view class, such as the following:
import UIKit
#IBDesignable
public class CustomControl: UIControl {
#IBOutlet public weak var textField: UITextField?
public var contentView: UIView?
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViewFromNib()
}
override public init(frame: CGRect) {
super.init(frame: frame)
setupViewFromNib()
}
override public func awakeFromNib() {
super.awakeFromNib()
setupViewFromNib()
}
override public func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupViewFromNib()
contentView?.prepareForInterfaceBuilder()
}
func setupViewFromNib() {
guard let view = loadViewFromNib() else { return }
guard let textField = self.textField else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let selfType = type(of: self)
let nibName = String(describing: selfType)
return Bundle(for: selfType)
.loadNibNamed(nibName, owner: self, options: nil)?
.first as? UIView
}
}
This custom view is being loaded into the Storyboards where they are to be used using the Storyboard Interface Builder.
The problem is that XCTest does not seem to model the descendants of these views, so when I am trying to write a test that involves typing text into the text field that is part of the custom view, the test bombs out with the error:
Neither element nor any descendant has keyboard focus.
Currently a work around appears to be to tap the keys on the keyboard instead of using the typeText method. However this is much slower (long pauses between key presses) and much more cumbersome test code wise.
The desired code:
let app = XCUIApplication()
let view = app.otherElements["customView"]
let textField = view.textFields["textField"]
textField.tap()
textField.typeText("12345")
Using test recording we get something like:
let app = XCUIApplication()
let view = app.otherElements["customView"]
view.tap()
app.typeText("12345")
But running this test causes the aforementioned error.
The edited / working test becomes:
let app = XCUIApplication()
let view = app.otherElements["customView"]
// view appears as a leaf with no descendants
view.tap()
app.keys["1"].tap()
app.keys["2"].tap()
app.keys["3"].tap()
app.keys["4"].tap()
app.keys["5"].tap()
I’m also not convinced this workaround will remain feasible if the custom view were to contain multiple controls, say perhaps for a date of birth control where I want more granular control over which field within the custom control I am using.
Is there a solution that allows me to access the fields within a custom view and potentially use the typeText method?
The problem has been solved. As advised by Titouan de Bailleul, the problem was that accessibility for the custom view had been enabled effectively hiding its descendant text fields.
Added sample project to Github:
https://github.com/stuartwakefield/XibXCTestIssueSample
Thanks Titouan.
I created a pair of xib file with the swift file(for that xib file).
[xib_template.xib & view_template.swift]
And I want to control this pair of xib file by my [main_VC.swift].
xib file have 1 button and 1 label.
I want to change the text of label when I click this button.
I want to set different template view and control them in my [main_VC].
But the #IBAction seems independent inside the class
I pass the value from [main_VC] to [view_template.swift] by init method searched on the internet.
I can get correct value by using func in [main_VC].
But when clicking the button,
the value is always nil.
The var inside IBAction cannot get the value from init.
I am new in swift and I tried my best but still cannot fix this.
How can I get the init value inside IBAction?
Or how can I programmatically create & disable the Ibaction from [main_VC]?
I adjusted my code to be more easy to read.
May have some little typing error.
I searched online and tried all I can already.
One people asked similar question before but have no answer.
Hope for help.
Thanks very much.
[view_template.swift]
import UIKit
class View_template_empty: UIView {
var _uid: String?
#IBOutlet weak var labellabel: UILabel!
init (uid: String) {
self._uid = uid
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
required init?(coder aDecoder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
#IBAction func clickingPanel2(_ sender: Any) {
print(self._uid) // always nil !!!!!!
self.labellabel.text = “test”
}
fun test () {
print(self._uid) // correct value
}
}
[main_VC] (only copy out the main function)
func allocator (_uid: String, uiView: UIView) {
switch templateType {
case “one”:
if let loading_panels = Bundle.main.loadNibNamed("xib_template", owner: uiView, options: nil)?.first as? view_template {
loading_panels.translatesAutoresizingMaskIntoConstraints = false
uiView.addSubview(loading_panels)
loading_panels.leadingAnchor.constraint(equalTo: uiView.leadingAnchor).isActive = true
loading_panels.trailingAnchor.constraint(equalTo: uiView.trailingAnchor).isActive = true
loading_panels.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
loading_panels.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
let view_temp = view_template(uid: _uid)
view_temp.test()
}
case “two”:
if let loading_panels = Bundle.main.loadNibNamed("xib_template_two”, owner: uiView, options: nil)?.first as? view_template_two {
loading_panels.translatesAutoresizingMaskIntoConstraints = false
uiView.addSubview(loading_panels)
loading_panels.leadingAnchor.constraint(equalTo: uiView.leadingAnchor).isActive = true
loading_panels.trailingAnchor.constraint(equalTo: uiView.trailingAnchor).isActive = true
loading_panels.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
loading_panels.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
}
default:
print("error")
}
You are using different initializers here:
When you say let view_temp = view_template(uid: _uid), init (uid: String) is used and your implementation sets _uid so it is not nil.
When you load a view from a XIB, init?(coder aDecoder: NSCoder) is used and this does not set _uid so it is nil.
To inject _uid into your templates, simply say loading_panels._uid = _uid in your two if let loading_panels = ... blocks.
You might also want to read section "Follow case conventions" in the Swift API Design Guidelines to brush up on your naming.
I've made an in-app custom keyboard that replaces the system keyboard and pops up when I tap inside a UITextField.
Here is my code:
class ViewController: UIViewController {
var myCustomKeyboard: UIView!
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let keyboardNib = UINib(nibName: "Keyboard", bundle: nil)
myCustomKeyboard = keyboardNib.instantiateWithOwner(self, options: nil)[0] as! UIView
textField.inputView = myCustomKeyboard
}
}
The keyboard layout is loaded from an xib file.
Question
How do I get the button text into the text field?
Notes:
There are many tutorials about making a custom system keyboard (which needs to be installed), but I only want an in-app keyboard. The tutorials use a special view controller just for the keyboard, but here it seems that I am just setting the keyboard view.
I have read the Custom Views for Data Input documentation.
This is the closest Stack Overflow question I could find, but it doesn't go as far as describing how to get the text from the buttons.
Update
This tutorial seems to indicate that there is a view controller for the custom input view. However, I am getting lost in the Objective-C code. What would be the process in Swift?
This answer mentions the UIKeyInput protocol that UITextField conforms to, but how do I use it?
If there is any built in way too make a custom in-app keyboard, I would really prefer that to making a normal custom view.
I imagine something like this:
A new function to handle button event
func updateTextfield(sender: UIButton) {
textField.text = (textField.text ?? "") + (sender.titleForState(.Normal) ?? "")
}
And after init your custom keyboard, register the buttons:
myCustomKeyboard.subviews
.filter { $0 as? UIButton != nil } // Keep the buttons only
.forEach { ($0 as! UIButton).addTarget(self, action: "updateTextfield", forControlEvents: .TouchUpInside)}
Setup
Make an xib file that includes all your keys
Use Autolayout so that the keys will resize to the correct proportions no matter how big the keyboard is set to later.
Create a swift file with the same name as the xib file and set it as the file owner in the xib file setting.
Hook up all the key buttons to an IBAction method in the swift file. (See the code below.)
Code
I'm using the delegate pattern to communicate between the custom keyboard view and the main view controller. This allows them to be decoupled. Multiple different custom keyboards could be swapped in and out without needing to change the detailed implementation code in the main view controller.
Keyboard.swift file
import UIKit
protocol KeyboardDelegate {
func keyWasTapped(character: String)
}
class Keyboard: UIView {
var delegate: KeyboardDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
let xibFileName = "Keyboard" // xib extention not needed
let view = NSBundle.mainBundle().loadNibNamed(xibFileName, owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
#IBAction func keyTapped(sender: UIButton) {
self.delegate?.keyWasTapped(sender.titleLabel!.text!)
}
}
Main view controller
Note that the ViewController conforms to the KeyboardDelegate protocol that we created. Also, when creating an instance of the keyboard view, the height needs to be set but the width doesn't. Apparently setting the inputView of the text field updates the keyboard view width to the screen width, which is convenient.
class ViewController: UIViewController, KeyboardDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// get an instance of the Keyboard (only the height is important)
let keyboardView = Keyboard(frame: CGRect(x: 0, y: 0, width: 0, height: 300))
// use the delegate to communicate
keyboardView.delegate = self
// replace the system keyboard with the custom keyboard
textField.inputView = keyboardView
}
// required method for keyboard delegate protocol
func keyWasTapped(character: String) {
textField.insertText(character)
}
}
Sources
The suggestions in #ryancrunchi's comment were helpful.
This answer from Creating a reusable UIView with xib (and loading from storyboard)
Related
A Swift example of Custom Views for Data Input (custom in-app keyboard)