Cannot pass 'var' by 'init' into '#IBAction' - ios

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.

Related

Accessibility Increment and Decrement not called for UISlider

I'm trying to make my app more accessible for Voice Over users. I have a slider that has numbers 1-100. If a user with Voice Over turned on swipes up or down to change the value, several numbers are being skipped. This means that an exact number is not able to be set. I'm following the suggestion from this site on subclassing UISlider and overriding accessibilityIncrement() and accessibilityDecrement() but they do not get called when the slider value changes. Below is my subclassed slider. Any idea why the methods are not getting called?
class FontSizeSlider: UISlider {
required init?(coder: NSCoder) {
super.init(coder: coder)
self.isAccessibilityElement = true
self.accessibilityTraits.insert(.adjustable)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func accessibilityIncrement() {
self.value += 1
self.sendActions(for: .valueChanged)
}
override func accessibilityDecrement() {
self.value -= 1
self.sendActions(for: .valueChanged)
}
}
This is something I need to know for work, so this was a fantastic exercise for me. Thank you for posting the question. Anyway, I got it to work after taking a peek at this page on Apple's website.
I could not get the increment/decrement methods to be called, either. I suspect they're stepper-specific. The value property, OTOH, gets called.
Here's the code I came up with to get it to work:
class FontSizeSlider: UISlider {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isAccessibilityElement = true
accessibilityLabel = "Font Size Slider"
accessibilityIdentifier = "fontSizeSlider"
// accessibilityIdentifier = AccessibilityConstants.fontSizeSlider.rawValue
minimumValue = 0
maximumValue = 100
isContinuous = true
}
override var accessibilityValue: String? {
get {
return sliderValueString
}
set {
super.accessibilityValue = sliderValueString
}
}
override var accessibilityTraits: UIAccessibilityTraits {
get {
return .adjustable
}
set {
super.accessibilityTraits = newValue
}
}
// Nobody needs to know about this outside the class, so marked it private
private var sliderValueString: String {
let stringValue = String(Int(value))
return "The font size is \(stringValue)"
}
}
You'll notice I used the setup() method, which does the same stuff for both initializers. You can tweak your values as you see fit for the min/max values.
You'll note I added accessibilityLabel, so it doesn't read off that it's a generic slider. I added the accessibilityIdentifier in there, too. That's something that can be used for UI tests so the element can be identified.
You'll probably want to put the accessibilityIdentifier somewhere where "everyone" can see it. Perhaps an enum. Here's what the enum implementation would look like:
enum AccessibilityConstants: String {
case fontSizeSlider
}
// Usage
accessibilityIdentifier = AccessibilityConstants.fontSizeSlider.rawValue
I overrode the accessibilityValue with a custom setter and getter. Additionally, I created a computed var for the string that's read off when the accessibilityValue is updated. Here's the code for that portion of it. Note I made it private because nobody outside the class needs to know about it:
// I adapted this from Apple's accessibility page that I posted above
override var accessibilityValue: String? {
get {
return sliderValueString
}
set {
super.accessibilityValue = sliderValueString
}
}
private var sliderValueString: String {
let stringValue = String(Int(value))
return "The font size is \(stringValue)"
}
One last thing...you don't need self everywhere unless you're accessing a property of your custom UISlider inside a closure like an animation block or a completion block.
Update
Deleted...
Update 2
So let's say you're on your viewController, you could add a target action to the slider, like so:
slider.addTarget(self, action: #selector(doSomething), for: .valueChanged)
#objc func doSomething() {
print("How much wood could a wood chuck chuck if a wood chuck could chuck wood")
}
Whenever value changes, your selector will get called.

How to render .xib (custom view) programmatically by NSClassFromString?

I am new to Swift.
Currently I have made serval xib files and they can be rendered in the following codes
let mySubview:customView = customView(frame: CGRect(x:10,y:300, width: 312, height:355))
self.view.addSubview(mySubview)
"customView" is the custom view (.xib) file while there are many others. However, I want to render it with a function parameter. I have used string but I got an error for this:
func addComp(name:String){
let className = NSClassFromString("MyApp."+name) as! UIView.Type
let subview = className.init()
subview.frame = CGRect(x:10,y:300, width: 312, height:355)
self.view.addSubview(subview)
}
It says
"Fatal error: Unexpectedly found nil while unwrapping an Optional value"
Anyway, is there any ways to define a custom view with function parameters? Either with string or any other methods.
I'd say you're coming at this wrong. Don't turn a string to a type; use a type directly.
Here's a UIView subclass with a factory method that does everything your addComp does:
class MyFactoryView : UIView {
required override init(frame:CGRect) {
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
}
static func make(andAddTo v:UIView) {
let subv = self.init(frame:CGRect(x:10,y:300, width: 312, height:355))
v.addSubview(subv)
}
}
Okay, so let's say we have some subclasses of that type:
class MyView1 : MyFactoryView {}
class MyView2 : MyFactoryView {}
So now there's no need for any string. To enact our little drama, we just talk directly to the type; for example:
MyView1.make(andAddTo:self.view)

Accessing descendant controls in custom UIView using XCTest

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.

Want to achieve the Keyboard selection through #IBInspectable

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.

Custom UIView subclass with XIB in Swift

I'm using Swift and Xcode 6.4 for what it's worth.
So I have a view controller that will be containing some multiple pairs of UILabels and UIImageViews. I wanted to put the UILabel-UIImageView pair into a custom UIView, so I could simply reuse the same structure repeatedly within the aforementioned view controller. (I'm aware this could be translated into a UITableView, but for the sake of ~learning~ please bear with me). This is turning out to be a more convoluted process than I imagined it would be, I'm having trouble figuring out the "right" way to make this all work in IB.
Currently I've been floundering around with a UIView subclass and corresponding XIB, overriding init(frame:) and init(coder), loading the view from the nib and adding it as a subview. This is what I've seen/read around the internet so far. (This is approximately it: http://iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6).
This gave me the problem of causing an infinite loop between init(coder) and loading the nib from the bundle. Strangely none of these articles or previous answers on stack overflow mention this!
Ok so I put a check in init(coder) to see if the subview had already been added. That "solved" that, seemingly. However I started running into an issue with my custom view outlets being nil by the time I try to assign values to them.
I made a didSet and added a breakpoint to take a look...they are definitely being set at one point, but by the time I try to, say, modify the textColor of a label, that label is nil.
I'm kind of tearing my hair out here.
Reusable components seem like software design 101, but I feel like Apple is conspiring against me. Should I be looking to use container VCs here? Should I just be nesting views and having a stupidly huge amount of outlets in my main VC? Why is this so convoluted? Why do everyone's examples NOT work for me?
Desired result (pretend the whole thing is the VC, the boxes are the custom uiviews I want):
Thanks for reading.
Following is my custom UIView subclass. In my main storyboard, I have UIViews with the subclass set as their class.
class StageCardView: UIView {
#IBOutlet weak private var stageLabel: UILabel! {
didSet {
NSLog("I will murder you %#", stageLabel)
}
}
#IBOutlet weak private var stageImage: UIImageView!
var stageName : String? {
didSet {
self.stageLabel.text = stageName
}
}
var imageName : String? {
didSet {
self.stageImage.image = UIImage(named: imageName!)
}
}
var textColor : UIColor? {
didSet {
self.stageLabel.textColor = textColor
}
}
var splatColor : UIColor? {
didSet {
let splatImage = UIImage(named: "backsplat")?.tintedImageWithColor(splatColor!)
self.backgroundColor = UIColor(patternImage: splatImage!)
}
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = NSBundle.mainBundle().loadNibNamed("StageCardView", owner: self, options: nil).first as? StageCardView {
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
EDIT: Here's what I've been able to get so far...
XIB:
Result:
Problem: When trying to access label or image outlets, they are nil. When checking at breakpoint of said access, the label and image subviews are there and the view hierarchy is as expected.
I'm OK with doing this all in code if thats what it takes, but I'm not huge into doing Autolayout in code so I'd rather not if there's a way to avoid it!
EDIT/QUESTION SHIFT:
I figured out how to make the outlets stop being nil.
Inspiration from this SO answer: Loaded nib but the view outlet was not set - new to InterfaceBuilder except instead of assigning the view outlet I assigned the individual component outlets.
Now this was at the point where I was just flinging shit at a wall and seeing if it'd stick. Does anyone know why I had to do this? What sort of dark magic is this?
General advice on view re-use
You're right, re-usable and composable elements is software 101. Interface Builder is not very good at it.
Specifically, xibs and storyboard are great ways to define views by re-using views that are defined in code. But they are not very good for defining views that you yourself wish to re-use within xibs and storyboards. (It can be done, but it is an advanced exercise.)
So, here's a rule of thumb. If you are defining a view that you want to re-use from code, then define it however you wish. But if you are defining a view that you want to be able to re-use possibly from within a storyboard, then define that view in code.
So in your case, if you're trying to define a custom view which you want to re-use from a storyboard, I'd do it in code. If you are dead set on defining your view via a xib, then I'd define a view in code and in its initializer have it initialize your xib-defined view and configure that as a subview.
Advice in this case
Here's roughly how you'd define your view in code:
class StageCardView: UIView {
var stageLabel = UILabel(frame:CGRectZero)
var stageImage = UIImageView(frame:CGRectZero)
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
stageImage.image = UIImage(named:"backsplat")
self.addSubview(stageLabel)
self.addSubview(stageImage)
// configure the initial layout of your subviews here.
}
}
You can now instantiate this in code and or via a storyboard, although you won't get a live preview in Interface Builder as is.
And alternatively, here's roughly how you might define a re-usable view based fundamentally on a xib, by embedding the xib-defined view in a code-defined view:
class StageCardView: UIView {
var embeddedView:EmbeddedView!
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
self.embeddedView = NSBundle.mainBundle().loadNibNamed("EmbeddedView",owner:self,options:nil).lastObject as! UIView
self.addSubview(self.embeddedView)
self.embeddedView.frame = self.bounds
self.embeddedView.autoresizingMask = .FlexibleHeight | .FlexibleWidth
}
}
Now you can use the code-defined view from storyboards or from code, and it will load its nib-defined subview (and there's still no live preview in IB).
I was able to work it around but the solution is a little bit tricky. It's up to debate if the gain is worth an effort but here is how I implemented it purely in interface builder
First I defined a custom UIView subclass named P2View
#IBDesignable class P2View: UIView
{
#IBOutlet private weak var titleLabel: UILabel!
#IBOutlet private weak var iconView: UIImageView!
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
#IBInspectable var image: UIImage? {
didSet {
if iconView != nil {
iconView.image = image
}
}
}
override init(frame: CGRect)
{
super.init(frame: frame)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func awakeFromNib()
{
super.awakeFromNib()
let bundle = Bundle(for: type(of: self))
guard let view = bundle.loadNibNamed("P2View", owner: self, options: nil)?.first as? UIView else {
return
}
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
let bindings = ["view": view]
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"H:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
addConstraints(verticalConstraints)
addConstraints(horizontalConstraints)
}
titleLabel.text = title
iconView.image = image
}
This is how it looks like in interface builder
This is how I embedded this custom view in the example view controller defined on a storyboard. Properties of P2View are set in the attributes inspector.
There are 3 points worth mentioning
First:
Use the Bundle(for: type(of: self)) when loading the nib. This is because the interface builder renders the designables in the separate process which main bundle is not the same as your main bundle.
Second:
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
When combining IBInspectables with IBOutlets you have to remember that the didSet functions are called before awakeFromNib method. Because of that, the outlets are not initialized and your app will probably crash at this point. Unfortunatelly you cannot omit the didSet function because the interface builder won't render your custom view so we have to leave this dirty if here.
Third:
titleLabel.text = title
iconView.image = image
We have to somehow initialize our controls. We were not able to do it when didSet function was called so we have to use the value stored in the IBInspectable properties and initialize them at the end of the awakeFromNib method.
This is how you can implement a custom view on a Xib, embed it on a storyboard, configure it on a storyboard, have it rendered and have a non-crashing app. It requires a hack, but it's possible.

Resources