I have created a TableView that contains UIViews, which hold a few other elements. These UIViews are created dynamically, as the data is called from a server. Inside each UIView there is a UILabel and a UIButtton. Once the button is clicked, I would like to have the corresponding label updated with some value. I was able to modify the UIButton and the view itself but unable to modify the UILabel. Here is an example of the method that is called when a UIButton is called. Right now it will change the background color of the corresponding UIView, but the label element does not work as intended. How can accomplish modifying this label element which is a subelement of the UIView?
- (void) heartPlus:(id)sender {
UIButton *button = (UIButton*) sender;
NSInteger id_num = button.tag;
UIView * view = (UIView *)[self.view.superview viewWithTag:id_num];
UILabel * label = (UILabel *)[view viewWithTag:id_num];
label.backgroundColor = [UIColor blueColor];
}
Creating the UIView along with adding the corresponding elements.
UIView * msgView = [[[UIView alloc] initWithFrame:CGRectMake(0,offSet,320,120)] init];
[msgView setTag:someID];
// Add button
UIButton * buttonUpdate = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonUpdate.tag = someID;
[buttonUpdate addTarget:self action:#selector(heartPlus:) forControlEvents:UIControlEventTouchUpInside];
UILabel * labelHeart = [[[UILabel alloc] initWithFrame:CGRectMake(280,100,20,10)] init];
labelHeart.tag = someID;
// Add each element to the msgView
[msgView addSubview:buttonUpdate];
[msgView addSubview:labelHeart];
There is still some missing code, but as far as I can see you have the error in heartPlus method and viewWithTag does not return the correct view. Therefore the assignment of backgroundColor fails. The explanation is below:
You are first getting the parent view of view that contains UIButton and UILabel. I assume your view hierarchy looks something like this:
UITableViewCell
UIView (tag: someID)
UILabel (tag: someID)
UIButton (tag: someID)
So, if I assume there is no error in the line below and that it returns correct UITableViewCell (or whatever other view is the parent of both UILabel and UIButton):
UIView * view = (UIView *)[self.view.superview viewWithTag:id_num];
We can clearly see the problem with both UILabel and UIButton having the same tags. So if you ask the UITableViewCell for the view with tag, it will simply fail - returns nil.
There are multiple solutions to this problem, but the problem remains the same.
Do not give the UILabel the same tag as UIButton.
I assume you are a beginner with Objective-C so I would suggest you first look into some tutorials of how UIViews work. But to make it easier for you, here are few options:
Create a custom subclass of the UIView with UIView properties.
Use unique tags for UILabel and for UIButton. To find the correct id_num on click, use introspection and sender's superview, which is passed to you in a method.
You could easily loop through UIView's subviews property (which is an array) and find the UILabel manually - this works, if there is only one UILabel or you are looking for all of them.
But in either case, you need to rethink of how UITableView works. It is not a good practice to ask self.view.superview for viewWithTag and your method could already fail at this point.
Related
I am trying to customize the SearchBar using the SearchBar reference as declared bellow
#property (weak, nonatomic) IBOutlet UISearchBar *searchRef;
Then, i am trying to access the UIImage Reference and UISearchBarTextFieldLabel as shown in picture
I am printing the reference of the subviews using following code
NSLog(#"%#",self.searchRef.subviews[0].subviews[1].subviews);
but not getting the result as expected. I want the UIImage ref as well as UISearchBarTextFieldLabel. How can i do that?
If you have a reference to the text field, you can cast it as UITextField and then access its leftView and rightView properties. Usually, the leftView is the magnifying glass UIImageView.
UITextField *textField = (UITextField *)self.searchRef.subviews[0].subviews[1];
// Change the search image.
if ([textField.leftView isKindOfClass:[UIImageView class]]) {
UIImageView *searchImageView = (UIImageView *)textField.leftView;
// Do something with the image view here...
}
As a side note, it's not recommended to assume that UISearchBar's views will always be laid out this way. For example, Apple may decide in future to change the view hierarchy, which could cause your code to fail or crash completely.
If you must do this, it would be safer to recursively search each view's subviews until you find what you're looking for, instead of hard-coding array indices.
You can reach the imageView from searchbar like this than you can reach the uiimage
Objective-C
UITextField *textFieldInsideSearchBar = [self.searhBar valueForKey:#"searchField"];
UIImageView *imageView = (UIImageView *) textFieldInsideSearchBar.leftView;
Swift 3 and Swift 4
let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField
let imageView = textFieldInsideSearchBar?.leftView as! UIImageView
The following does not work
UILabel *titleLabel = (UILabel *)self.navigationController.navigationItem.titleView;
OR
UILabel *titleLabel = (UILabel *)self.navigationItem.titleView;
Both return nil.
Yes, the title in question is in the navigation bar. The objective is simple: I want the UILabel. Can that be done?
UPDATE
One of the reasons I needed the UILabel is so that I could grab its width after I fill it with text, which would essentially tell me the max width of the label (basically a device agnostic way of measuring: whether iPad, iPhone, etc).
The short answer: You can't. UINavigationBar 's internals handle the display of a UIViewController's title. The titleView property of UINavigationItem is used for other purposes.
The long answer
Look at UINavigationBar.h and get a rough understanding of how they both work together. - You'll quickly see why the titleView has nothing to do with a viewController's title.
UINavigationItem is the dataSource for a UINavigationBar.
It holds the barButtonItems, the title and the titleView for each UIViewController.
Look at the UIViewController (UINavigationControllerItem) category defined in UINavigationController.h there is a good explanation in the comments there.
The UINavigationBar on the other hand is 'simply' displaying the content of UINavigationItems.
When you push a viewController onto the stack of a navigationController, the navigationController implicitly calls UINavigationBar: pushNavigationItem:animated: on its navigationBar, sending the UINavigationItem of the viewController you are pushing.
I see you have the same misconception that i had when i started digging into UINavigationControllers:
The fact that you can write things like UILabel *titleLabel = (UILabel *)self.navigationController.navigationItem.titleView; without any warning is very misleading
UINavigationController does not have a UINavigationItem. It will never have.
This is because a navigationController will never be contained inside another navigationController. (If you try to build this containment in IB it will give you a warning i think !?) The reason you can call self.navigationController.navigationItem is that the category i mentioned above is giving EVERY UIViewController a UINavigationItem property. UINavigationController in turn IS a UIViewController and thus receives the property from the category within its own implementation. - That's very confusing!
I assume you want to grab a reference to the titleLabel (even though you can't !), because you want to set the font or the textcolor of the title. If so, you can do these things through UINavigationBar: setTitleTextAttributes:.
If you want to use a complete custom titleView, you are better off creating a UILabel and assigning it to the titleView property of the UINavigationItem
this works for me:
- (UILabel *)getNavBarTitleLabel
{
UINavigationBar *navBar = self.navigationController.navigationBar;
if (!navBar) { return nil; }
for (UIView *subview in navBar.subviews) {
for (UIView *subSubview in subview.subviews) {
if ([subSubview isMemberOfClass:[UILabel class]])
{
// consider adding more checks to verify that this UILabel is indeed the title label before returning it
// for example, if you know what the text should be you can compare the strings
return (UILabel*)subSubview;
}
}
}
return nil;
}
In my app, I have a list of tags corresponding to a user that I would like to display. The tag should have a text field and a button on the right hand side to delete the tag (much like the tags on this site when asking a question!)
I only want part of the tag to be clickable, so I don't want to place a UILabel inside a UIButton, and it seems that adding a UIButton to a UILabel is bad practice. What is the best way to create this object?
I want something similar to a UITableViewCell, but not a UITableViewCell because it won't be displayed in a table view.
Just to create a UIView subclass, we could name it TagView, give UILabel and UIButton to display, and don't forget to create a delegate protocol to for action response.
Maybe it like this.
#protocol TagViewDelegate
#optional
- (void)didSelectedTagView:(TagView *)tagView;
- (void)didTagViewAccessoryButtonTapped:(TagView *)tagView;
#end
#interface TagView : UIView
{
UILabel *label;
UIButton *accessoryButton;
}
#property (weak, nonatomic) id delegate;
#property (readonly, nonatomic) UILabel *label;
#end
Then implement a UITapGestureRecognizer for label and touchUpInside event for button
- (void)initUIElement {
// give it a gestureRecognizer for label to response the label did select
UITapGestureRecognizer *labelTapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didSelectedLabel:)];
[label addGestureRecognizer:labelTapGR];
[accessoryButton addTarget:self action:#selector(accessoryTapped:) forControlEvents:UIControlEventTouchUpOutside];
}
in the method didSelectedLabel: and accessoryTapped: , give response to tagView's delegate like this.
- (void)didSelectedLabel:(UITapGestureRecognizer *)gestureRecognizer
{
[self.delegate didSelectedTagView:self];
}
- (void)accessoryTapped:(id)sender
{
[self.delegate didTagViewAccessoryButtonTapped:self];
}
There is a library you may take a look.
DWTagList
Just like cabellicar123 said, use a UIView and place your UILabel and UIButton inside it. You have a couple of ways to do this.
First, you can make a XIB file with the main view being your UIView, and just drag into it a UIButton and a UILabel and position them the way you want, and then in code load the nib when you need it.
Second is to create this UIView programmatically, and use the addSubview method and add your UILabel and UIButton that way. Make sure you have defined these two subviews with initWithFrame if you are using this method, so that they are correctly placed WITHIN the UIView. What I mean by this is that the origin of these views should be relative to the top left corner of the UIView.
I have written some code which toggles 2 buttons depending on which one is selected. If the UK one is selected it becomes ticked and the BR one becomes unticked, and vice versa. However, this only seems to be the case for the UK button. If I select the BR button than the UK button unticks, the BR button briefly ticks but then it unticks again.
I have linked up my buttons correctly (I have triple checked), and as the BR button briefly ticks it is definitely linked up. The code I am using is below:
.h
#property (weak) IBOutlet UIButton *btUK;
#property (weak) IBOutlet UIButton *btBR;
.m
- (IBAction)changePortal:(id)sender
{
UIButton *button = (UIButton *)sender;
if (button.tag == kUKButton)
{
self.btUK.imageView.image = [UIImage imageNamed:#"tick_box.png"];
self.btBR.imageView.image = [UIImage imageNamed:#"tick_box_empty.png"];
[Singleton sharedSingleton].bUseUKPortal = YES;
}
else if (button.tag == kBRButton)
{
self.btBR.imageView.image = [UIImage imageNamed:#"tick_box.png"];
self.btUK.imageView.image = [UIImage imageNamed:#"tick_box_empty.png"];
[Singleton sharedSingleton].bUseUKPortal = NO;
}
}
I have set break points within the code and have confirmed that both the buttons go in to their relevant sections when clicked. I can also confirm that no other code is using the btUK and btBR variables as I have just written it all.
Both buttons have changePortal set as their action, and the function is only called once per click.
I have also tried cleaning the code but this did not fix my issue.
If anyone can shed any light as to why this is happening then I would be very grateful.
The correct way to set the image of a UIButton is to call setImage:forState:.
So try to alter your code to something like this:
// Follow this pattern for every button image change
[self.btUK setImage:[UIImage imageNamed:#"tick_box.png"] forState:UIControlStateNormal];
Now regarding the imageView property the documentation states that:
The button’s image view. (read-only)
#property(nonatomic, readonly, retain) UIImageView *imageView
Discussion
Although this property is read-only, its own properties are
read/write. Use these properties to configure the appearance and
behavior of the button’s view. For example:
UIButton *button = [UIButton buttonWithType: UIButtonTypeRoundedRect];
button.imageView.exclusiveTouch = YES;
The imageView property returns a value even if the button has not been
displayed yet. The value of the property is nil for system buttons.
How can I add a clear button (cross inside a circle) for UITextView like UITextField has?
Based on the answer from GhostRider a more accurate and up to date implementation:
int kClearButtonWidth = 15;
int kClearButtonHeight = kClearButtonWidth;
//add the clear button
self.clearButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.clearButton setImage:[UIImage imageNamed:#"UITextFieldClearButton.png"] forState:UIControlStateNormal];
[self.clearButton setImage:[UIImage imageNamed:#"UITextFieldClearButtonPressed.png"] forState:UIControlStateHighlighted];
self.clearButton.frame = CGRectMake(0, 0, kClearButtonWidth, kClearButtonHeight);
self.clearButton.center = CGPointMake(self.textView.frame.size.width - kClearButtonWidth , kClearButtonHeight);
[self.clearButton addTarget:self action:#selector(clearTextView:) forControlEvents:UIControlEventTouchUpInside];
[self.textView addSubview:self.clearButton];
And the method
- (void)clearTextView:(id)sender{
self.textView.text = #"";
}
You can use this images for the two states of the button:
just make a uibutton and put it on uitextview and set its action for clear text view;
uitextview.frame = (0,0,320,416);
uibutton.frame = (310,0,10,10);
[uibutton setimage:#"cross.png" forcontrolstate:uicontrolstatenoraml];
[uibutton addTarget:self action:#selector(clearButtonSelected:) forControlEvents:UIControlEventTouchUpInside];
-(void)clearButtonSelected{
uitextview=#"";
}
hope you want to clear the text view text when you click on cross button above is help
if not understand then i can send you proper program for that
From product perspective, if you're going to have a clear button, you probably want to use a UITextField instead of a UITextView and UITextField supports a clear button natively - set the clearButtonMode property as such:
UITextField *textfield = ...;
textfield.clearButtonMode = UITextFieldViewModeAlways;
See screenshot:
You could use UITextFieldViewModeWhileEditing to only present the clear button while the user is actively updating the content.
There's nothing built in like there is for the UITextField. You'd have to add the view yourself (probably a UIButton) and place it correctly and also somehow get the text to wrap around it correctly. (And I don't think the latter is really possible.)
Maybe instead you should display a toolbar above the keyboard (or an inputAccessoryView if you're targeting 3.2 and later) that provides a clear button.
For me changing the .frame or the .contentInset properties did not work.
For me the best result came from:
1) adding a UIView to the controller, give it round corners and a border to mimic a UITextView.
self.viewTextBackground.layer.borderColor = [UIColor colorWithRed:171/255.0 green:171/255.0 blue:171/255.0 alpha:1.0].CGColor;
self.viewTextBackground.layer.borderWidth = 1.0f;
self.viewTextBackground.layer.cornerRadius = 9.0f;
2) place UITextView on top of this UIView. Place it so that the borders of the underlying UIView stay visible.
3) give the UITextView round corners:
self.textNote.layer.cornerRadius = 9.0f;
3) Make its width fe. 30pixels less compared to the underlying UIView. You now have space for a clear-button.
4) simply add a UIButton to your controller and place it in the top-right corner of the underlying UIView.
5) change the buttons properties: set its type to 'custom' and set its image to the image of a grey cross.
6) bind an action to the button te clear the UITextView
You can add a clear button like the one in the attached screenshot with minimal coding. Just follow these steps:
Select the storyboard and drag a UIButton into your UITextView
Set the buttons constraints
Assign a title or a background image
Create the button's IBOutlet reference and the action (see onClearPressed(_:) below) for "Touch Up Inside" in the ViewController
Implement the textViewDidChange(_:) UITextViewDelegate delegate method, and make sure to set the button's isEnabled property based on the textfield content, e.g.:
func textViewDidChange(_ textView: UITextView) {
clearButton.isEnabled = !textView.text.isEmpty
}
Implement the onClearPressed(_:) action:
#IBAction func onClearPressed(_ sender: Any) {
textView.text = ""
clearButton.isEnabled = false
}
That's it.