Animation difference between UIView and UIButton - ios

In my custom table view cell, I have 4 buttons with an animation (shown below). The problem is when I'm using an UIButton, the animation doesn't animate as I wanted. But when I use an UIView, it works exactly as how I want it.
The code is exactly the same with only the difference of using a different type of UIView.
This animation is using an UIButton:
This animation is using an UIView
To make things a bit more clear, the only thing I've replaced in the code is:
// Test with Buttons
let button1 = Button() // Subclass of UIButton
let button2 = Button()
// Test with UIViews
let button1 = UIView()
let button2 = UIView()
Question:
Can someone tell me why a UIButton behaves differently compared to a normal UIView?
Initially I thought by not posting the code, I could make the question easier to read as both tests are using exactly the same code (except for the "element" (element being UIView or UIButton), and thought perhaps the problem lies in the difference between the "elements". I realize now that this was my mistake.
My code:
class CustomView: UIView {
private var base: [NSLayoutConstraint] = []
private var open: [NSLayoutConstraint] = []
var buttons: [UIView] = []
private var active = false
override init(frame: CGRect) {
super.init(frame: frame)
let button1 = CustomButton(frame: CGRectZero, color: UIColor.yellowColor().CGColor)
let button2 = CustomButton(frame: CGRectZero, color: UIColor.redColor().CGColor)
// let button1 = UIView(); button1.backgroundColor = UIColor.yellowColor()
// let button2 = UIView(); button2.backgroundColor = UIColor.redColor()
let views = ["button1": button1, "button2": button2]
buttons = [button1, button2]
buttons.enumerate().forEach {
$0.element.translatesAutoresizingMaskIntoConstraints = false
addSubview($0.element)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[button\($0.index + 1)]|", options: [], metrics: nil, views: views))
base += [NSLayoutConstraint(item: $0.element, attribute: .Width , relatedBy: .Equal, toItem: buttons.first!, attribute: .Width , multiplier: 1, constant: 0)]
}
open += [NSLayoutConstraint(item: buttons.last!, attribute: .Width, relatedBy: .Equal, toItem: buttons.first!, attribute: .Width, multiplier: 0.33, constant: 0)]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[button1]-0.5-[button2]|", options: [], metrics: nil, views: views))
addConstraints(base)
backgroundColor = .blackColor()
clipsToBounds = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func changeState() {
removeConstraints(active ? open : base); addConstraints(active ? base : open)
active = !active
UIView.animateWithDuration(0.5, animations: { self.layoutIfNeeded() })
}
}
Solution:
After posting the code and accidentally changing the background color of the buttons, I noticed that it was behaving accordingly. This made me realize I was using a CAShapeLayer in the buttons which is causing the behaviour seen in the first animation. Now I know what to fix. If this post should be closed or deleted, please tell me so. Then I will delete the answer. And thanks for those who tried to help!

Without you providing more details the only thing I can do is guessing that the behaviour you are witnessing comes down to buttons being less "elastic" than views, in that they have a "natural" size determined by their content (title and/or image) and auto layout really likes enforcing that natural size (as you can see from IB's warnings).
If you need more help, you should provide more details. Are you actually using auto layout? What constraints? What are you animating? etc.

Related

NSLayoutConstraints programmatically

I want to add constraints to a view programmatically.
This is what I did:
extension UIView {
func bottomToTop(other: UIView) {
self.translatesAutoresizingMaskIntoConstraints = false
other.translatesAutoresizingMaskIntoConstraints = false
let constraint = NSLayoutConstraint(
item: self,
attribute: .bottom,
relatedBy: .equal
toItem: other,
attribute: .top,
multiplier: 1.0,
constant: 0.0
)
superview?.addConstraint(constraint)
constraint.isActive = true
}
}
let label = UILabel()
label.text = "Lenaaaaa"
label.sizeToFit()
label.backgroundColor = .green
let label1 = UILabel()
label1.text = "Lena 2"
label1.sizeToFit()
label1.backgroundColor = .green
let uiView = UIView(frame: frame) (not zero)
uiView.addSubview(label)
uiView.addSubview(label2)
label.bottomToTop(label2)
Why do I end up with this?
Why do I end up with this?
Because your constraints are ambiguous. Once you add even one constraint that affects a view, you must describe that view's position and size in terms of autolayout completely. (And you must stop talking about .frame, as it is now effectively meaningless.)
Thus, you have said only
label.bottomToTop(label2)
But you have not said where the top of label is, where the left of label is, where the left of label2 is, and so on. Thus the autolayout engine throws up its hands in despair.
You could easily have discovered this just by running your app and using the view debugger. It puts up great big exclamation marks telling you what your autolayout issues are.

Custom UIView is initialized using init(coder), how do I initialize it using init(frame)

So I overrode the init(frame) to do a bunch of stuff:
class InputField: UIView {
override init(frame: CGRect) {
super.init(frame: frame);
let label = UILabel(frame: CGRect(x: 8, y: 8, width: 73, height: 30));
label.text = "I want";
label.font = UIFont(name: "Avenir-Book", size: 26.0);
addSubview(label);
NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leadingMargin, multiplier: 1.0, constant: 8.0).isActive = true;
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
}
The problem is that the second init() is called not the first one. The way I have it set up in storyboard is by having a plain empty UIView whose class is InputField. Why does it use the second one and not the first one?
I don't want to create the InputField programmatically as I'd much rather see the Views I'm setting up in the storyboard. I know how to if I'm stuck, but I mostly wanna learn how to do it this way.
edit: as stated in comment, the current workaround is doing that configuration code in init(coder). But this still doesn't answer: Why is that being called and not init(frame)?
There are different convenience methods to help initialize a view. When a view is actually initialized only one of the many methods(-frame, -coder, etc) are going to be invoked.
If you need to perform any custom initialization, you can have a common init method.

How to add interactive UILabels on top of a UIImageView?

I need to add few labels on top of an UIImageView. The labels' text can be changed by tapping on them. What is the best way to achieve this? I am using Swift programming language. Looking up some solutions on stackoverflow, I found a couple of walkthroughs that use String.drawInRect method to draw some text in a rectangle which is then placed on the UIImageView. But like this I don't think I will be able to change the text, or even recognize a touch event on them. Please help.
UPDATE
My code so far:
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImage(named: "Image")
let imgView = UIImageView(image: img)
self.view.addSubview(imgView)
var myLabel = UILabel()
myLabel.text = "Hello There"
myLabel.textColor = UIColor.redColor()
myLabel.font = UIFont(name: "Marker Felt", size: 20)
myLabel.accessibilityIdentifier = "this is good!"
myLabel.frame = CGRect(x: img!.size.width/2 /* - myLable.width / 2 ? */, y: 0, width: img!.size.width, height: 40)
imgView.addSubview(myLabel)
imgView.userInteractionEnabled = true
myLabel.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: "handlePanGesture:")
myLabel.addGestureRecognizer(tapGesture)
}
func handlePanGesture(sender: UITapGestureRecognizer) {
var senderView = sender.view as! UILabel
print(senderView.text)
senderView.text = "look how i changed!"
print(senderView.accessibilityIdentifier)
}
So far the results are positive I have an image with the label on top of it that can respond to touch events. Now I need to find the label's width so that I can effectively place it in the center when required. Then I need to find a way to place the labels at exact coordinates relative to the image's top left corner as origin.
Any help in these two tasks will be hugely appreciated.
Adding label on ImageView is best approach. but you can also do it by adding button on ImageView.
I created a example where i created a ImageView on storyboard and create its outlet in ViewController class and in viewDidLoad i created a label and add it to label and add UITapGestureRecognizer to label. when user taps label we changed the label text and it's position.
class ViewController: UIViewController {
#IBOutlet weak var winterImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(x: 10, y: 0, width: self.winterImageView.frame.width - 10, height: 30))
label.textColor = UIColor.redColor()
label.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
label.addGestureRecognizer(tapGesture)
label.text = "Is Winter is coming, My Friend?"
self.winterImageView.addSubview(label)
}
Change label text and position in handleTap
/// handle tap here
func handleTap(sender: UITapGestureRecognizer) {
let senderView = sender.view as! UILabel
senderView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: senderView, attribute: .CenterX, relatedBy: .Equal, toItem: self.winterImageView, attribute: .CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: senderView, attribute: .CenterY, relatedBy: .Equal, toItem: self.winterImageView, attribute: .CenterY, multiplier: 1, constant: 0).active = true
print(senderView.text)
senderView.text = "Yes!!! Winter is coming, My Friend!!"
}
You can download project from here InteractiveLabel
I can see from the other answers and comments related to one another virtually same stuff.If you familiar using Cocoa pods then you will agree with my opinion.Always,Just look around yourself and pick the best.If you want your project goes smooth and steady then JLStickerTextView is your friend and its way to go.It's free,elegant and more vibrant label customisation project available to everyone and the best thing about this project is written in handy Swift.
Github Link: https://github.com/luiyezheng/JLStickerTextView
Features
You can add multiple Text to StickerTextView at the same time
Multiple line Text support
Rotate, resize the text with one finger
Set the Color, alpha, font, alignment, TextShadow, lineSpacing...... of the text
StickerTextView also handle the process of rendering text on Image
Written in Swift
Note: In, My personal opinion.Way, the code been written in this projects simply superb and properly categorised.
Avaliable Text Attributes Reference:
MainView Screenshot from the project:
Output from my personal project based on JLStickerTextView.I, hope you will consider it.If you need any more information let me know...
github.com/khush004/StickerView/tree/master
here is code of JLStickerTextView which is error free with compatibility of swift 3.0
You can use a label and add a gesture recognizer from which you can set an action.
EDIT (responding to OP comment) :
Basically you put an UILabel on top of your card, set a gesture recognizer on it, and set a hidden UITextField at the same position as your label. This way when you tap on it, you specify in your gesture recognizer method that the UI must set label as hidden and textfield as visible. When you're done (end editing), just save your changes and update the UI.
If you just want to center align your UILabel and UIImageView, you can use AutoLayout constraint.
NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: imageView, attribute: .CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: imageView, attribute: .CenterY, multiplier: 1, constant: 0).active = true
func handlePanGesture(sender: UITapGestureRecognizer) {
let senderView = sender.view as! UILabel
print(senderView.text)
senderView.textColor = UIColor.redColor()
senderView.text = "look how i changed!"
print(senderView.accessibilityIdentifier)
}
Ouput :
sender.view?.frame
▿ Optional
▿ Some : CGRect
▿ origin : CGPoint
- x : 0.0
- y : 0.0 { ... }
▿ size : CGSize
- width : 335.0
- height : 28.0
I use CALayers, gesture recognizers, and the hitTest method of the layers. Sample code below:
class ImageView: UIImageView {
let tapGesture = UITapGestureRecognizer()
let redLayer = CATextLayer()
var redHitCounter:Int = 0
let greenLayer = CATextLayer()
var greenHitCounter:Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
setUpClickableLayers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpClickableLayers()
}
private func setUpClickableLayers() {
self.isUserInteractionEnabled = true
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(changeText))
self.addGestureRecognizer(tapGesture)
redLayer.frame = CGRect(x: 40, y: 40, width: 100, height: 40)
redLayer.backgroundColor = UIColor.red.cgColor
redLayer.string = String(redHitCounter)
redLayer.alignmentMode = kCAAlignmentCenter
self.layer.addSublayer(redLayer)
greenLayer.frame = CGRect(x: 40, y: 140, width: 100, height: 40)
greenLayer.backgroundColor = UIColor.green.cgColor
greenLayer.string = String(redHitCounter)
greenLayer.alignmentMode = kCAAlignmentCenter
self.layer.addSublayer(greenLayer)
}
internal func changeText(_ recognizer:UITapGestureRecognizer) {
let p = recognizer.location(in: self)
if (redLayer.hitTest(p) != nil) {
redHitCounter += 1
redLayer.string = String(redHitCounter)
} else if (greenLayer.hitTest(p) != nil) {
greenHitCounter += 1
greenLayer.string = String(greenHitCounter)
}
}
}
A few notes:
(1) Remember to set your UIImageView's isUserInteractionEnabled to true. It took me an hour to debug why my UIImageView was seeing gestures!
(2) The hitTest() method works for the CALayer and all subclasses. Just remember to make the layer large enough to work on fat fingers.
(3) You can also use the pan and pinch gestures to move, rotate, and resize the target layer.

How to create an editable UITextField programmatically [duplicate]

This question already has an answer here:
A UIButton in my subview won't work
(1 answer)
Closed 6 years ago.
I'm using Xcode 7.3.1 to write an app in Swift to run on iOS 9.3. I'm creating UITextFields programmatically, but can't get them to accept focus when I click in one of them. Here's how I create each UITextField:
self.hoursField = UITextField()
self.hoursField!.borderStyle = UITextBorderStyle.Bezel
self.hoursField!.canBecomeFirstResponder()
self.hoursField!.canBecomeFocused()
self.hoursField!.delegate = self
self.hoursField!.enablesReturnKeyAutomatically = true
self.hoursField!.translatesAutoresizingMaskIntoConstraints = false
self.hoursField!.userInteractionEnabled = true
The UITextFields appear exactly where I want them and look exactly as I want them to look, I just can't get them to accept focus so I can edit the text in the field.
I am implementing all the methods in the UITextFieldDelegate protocol, and returning true from all those methods that return a boolean.
Can anyone explain what I'm doing wrong, or not doing?
Each text field is being created and added to a custom subclass of UIView that contains a couple of UILabels in addition to the UITextField. Here's the relevant part of the custom view's init method that creates the text field:
init(frame: CGRect, charge: (code:String, note:String?, hours:Int)) {
super.init(frame: frame)
// Create the hours UITextField
self.hoursField = UITextField()
self.hoursField!.borderStyle = UITextBorderStyle.Bezel
self.hoursField!.canBecomeFirstResponder()
self.hoursField!.canBecomeFocused()
self.hoursField!.contentVerticalAlignment = UIControlContentVerticalAlignment.Center
self.hoursField!.delegate = self
self.hoursField!.enablesReturnKeyAutomatically = true
self.hoursField!.font = UIFont.systemFontOfSize(14)
self.hoursField!.keyboardType = UIKeyboardType.NumberPad
self.hoursField!.returnKeyType = UIReturnKeyType.Done
self.hoursField!.text = String(charge.hours)
self.hoursField!.textAlignment = NSTextAlignment.Right
self.hoursField!.translatesAutoresizingMaskIntoConstraints = false
self.hoursField!.userInteractionEnabled = true
// Add it to the view
self.addSubview(self.hoursField!)
// Create its layout constraints
let hoursTopMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.codeLabel!, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let hoursLeftMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.codeLabel!, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: self.hourFieldIndent)
let hoursRightMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.hoursField!, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: self.hourFieldWidth)
// Add the layout constraints to the view
self.addConstraints([hoursTopMarginConstraint, hoursLeftMarginConstraint, hoursRightMarginConstraint])
A stripped-down version of my app that shows the problem I'm having can be downloded from https://github.com/ThomBrando/TextFieldDemo.git
Have you tried
self.hoursField!.becomeFirstResponder()
When I implement a texfield programmatically all that I have to do is instantiate the texfield. let field = UITextFeild() set its delegate let field.delegate = self
extension UIViewController: UITextFieldDelegate {
public func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
return false
}
}
I have never used the two lines you had in your attempt.
.canBecomeFirstResponder
.conBecomeFocused
.enablesReturnKeyAutomatically
I would suggest removing those.
Then if you are trying to get the keyboard to dismiss when anywhere else is tapped on the screen I would suggest adding this wrapped up in an extension so you can add it anywhere.
extension OnboardingPage {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
Then just call hideKeyboardWhenTappedAround() in your viewDidLoad that you want that action on.
Hope that helps. :)
Can you give us a little more info? Like what's the frame of your UITextfield? Because i copied your code and i have the same issue but it was fine for me when i added a frame for UITextfield. Also just want to make sure you're adding the textfield as a subview to it's parent view.
Also I would recommend trying on a device if you have one, the simulator sometimes bugs out and things don't work as expected
I think this happens because you are testing it on the simulator with the keyboard disabled. The simulator will then use your computer's keyboard and won't show the iOS keyboard.
Could you try pressing Cmd-K when you select the UITextField?
The problem turns out to be this line:
let frame:CGRect = CGRect(x: 0, y: totalContentHeight, width: 0, height: 0)
let chargeView:CustomView = CustomView(frame: frame, charge: charge)
So your CustomView has zero width, zero height — zero size. Thus, none of its subviews is touchable. The text fields are its subviews. Thus, they are not touchable. They are visible, because the CustomView does not clip to its bounds; but they are outside their superview, so they are not touchable.
To prove this, just change the first line to this:
let frame:CGRect = CGRect(x: 0, y: totalContentHeight, width: 500, height: 100)
Those are not the "right" numbers, but that doesn't matter. The point is, you will find you can now tap on the text field and type in it! So that should give you enough of a clue to get started.

Add constraint before view is seen?

I am trying to center an arrow button with a width = 40 in the middle of my screen using constraints programmatically.
My code works if I call it in viewDidAppear but crashes in viewDidLoad.
Having it in viewDidAppear is an eye sore since you see the screen and then see the button jump to the middle of the screen. Any idea how to make it set up before the view is shown?
I might add this is a subview so I am calling centerArrow() from a viewController whose view is not the same view.
So inside the viewController:
let pview = NSBundle.mainBundle().loadNibNamed("Profile", owner: nil, options: nil)[0] as! ProfileView
override func viewDidAppear(animated: Bool) {
pview.centerArrow()
}
And inside ProfileView which inherits UIView:
func centerArrow()
{
let screenSize: CGRect = UIScreen.mainScreen().bounds
let width = (screenSize.width / 2) - 20
let constraint = NSLayoutConstraint(item: arrowButton, attribute: NSLayoutAttribute.LeftMargin, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.LeftMargin, multiplier: 1, constant: width)
self.addConstraint(constraint)
}
Try using viewDidLayoutSubViews, that might provide the timing you are looking for in this case.

Resources