Related
while presenting the search controller i am setting the showScopeBar property to hide or show the scope bar
- (void)willPresentSearchController:(UISearchController *)searchController {
// do something before the search controller is presented
NSMutableArray *scopeArray = #[#"All"].mutableCopy;
UISearchBar *searchBar =_searchController.searchBar;
if (![labelDepartmentSelection.text isEqualToString:#"Department"]) {
[scopeArray addObject:#"Department"];
}
if (![labelJobSelection.text isEqualToString:#"Job"]) {
[scopeArray addObject:#"Job"];
}
searchBar.scopeButtonTitles = scopeArray;
searchBar.showsScopeBar = scopeArray.count>1;
[searchBar layoutIfNeeded];
NSArray *subviews = searchBar.subviews;
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
//Get search bar with scope bar to reappear after search keyboard is dismissed
UIView *scopeBar = [subviews.firstObject subviews].firstObject;
[scopeBar setHidden:NO];
CGRect frame = scopeBar.frame;
frame.origin.y = frame.origin.y = 64.0;
scopeBar.frame = frame;
}
[searchBar layoutSubviews];
[searchBar sizeToFit];
[serviceOptionTableView setContentInset:UIEdgeInsetsMake(scopeArray.count>1?44:0, 0, 0, 0)];
serviceOptionTableView.tableHeaderView = searchController.searchBar;
}
and when i simply hide and show the scope bar base on the scope titles i want it giet distorted
here is the case for this
- (void)willPresentSearchController:(UISearchController *)searchController {
// do something before the search controller is presented
NSMutableArray *scopeArray = #\[#"All"\].mutableCopy;
UISearchBar *searchBar =_searchController.searchBar;
if (!\[labelDepartmentSelection.text isEqualToString:#"Department"\]) {
\[scopeArray addObject:#"Department"\];
}
if (!\[labelJobSelection.text isEqualToString:#"Job"\]) {
\[scopeArray addObject:#"Job"\];
}
searchBar.scopeButtonTitles = scopeArray;
searchBar.showsScopeBar = scopeArray.count>1;
\[serviceOptionTableView setContentInset:UIEdgeInsetsMake(scopeArray.count>1?44:0, 0, 0, 0)\];
}
this is the image of case 2
The issue should fix itself if you remove the line:
searchBar.showsScopeBar = scopeArray.count>1;
Setting the showScopeBar property has a weird effect for some reason. Removing it allows the default animation behaviour without any issues.
iOS 7.1 includes a new Accessibility setting calls Button Shapes that causes some button text to be automatically underlined. Is there a way to detect this mode, or customize it for individual UIButtons?
(This to allow changing button labels such as a dash or underscore so that when underlined, they don't look like an equals sign, etc.)
As of iOS 14, you can use UIAccessibility.buttonShapesEnabled or UIAccessibilityButtonShapesEnabled(), which will be true when the setting is enabled.
Old question, but hopefully this helps someone. There's still no built-in method for checking if Button Shapes is enabled on iOS, so we added this:
#pragma mark - Accessibility
/**
* There's currently no built-in way to ascertain whether button shapes is enabled.
* But we want to manually add shapes to certain buttons when it is.
*/
static BOOL _accessibilityButtonShapesEnabled = NO;
+ (BOOL)accessibilityButtonShapesEnabled {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self checkIfButtonShapesEnabled];
});
return _accessibilityButtonShapesEnabled;
}
+ (void)checkIfButtonShapesEnabled {
UIButton *testButton = [[UIButton alloc] init];
[testButton setTitle:#"Button Shapes" forState:UIControlStateNormal];
_accessibilityButtonShapesEnabled = (BOOL)[(NSDictionary *)[testButton.titleLabel.attributedText attributesAtIndex:0 effectiveRange:nil] valueForKey:NSUnderlineStyleAttributeName];
}
Because there's also no notification if Button Shapes is disabled/enabled whilst the app is running, we run checkIfButtonShapesEnabled in applicationDidBecomeActive:, and push our own notification if the value has changed. This should work in all cases, because it is not currently possible to add the Button Shapes toggle to the "Accessibility Shortcut".
It looks like you can request the button label's attributes and test to see if it contains an NSUnderlineStyleAttributeName attribute. If you remove the NSUnderlineStyleAttributeName attribute the system will put it right back so it seems the trick is to explicitly set the label's underline attribute to 0. I've added the following to my custom button:
- (void) adjustLabelProperties // override underline attribute
{
NSMutableAttributedString *attributedText = [self.titleLabel.attributedText mutableCopy];
[attributedText addAttribute: NSUnderlineStyleAttributeName value: #(0) range: NSMakeRange(0, [attributedText length])];
self.titleLabel.attributedText = attributedText;
}
I know it's an old question but this code works. Tested in iOS 9.3
NSMutableAttributedString *attrStr = [btn.titleLabel.attributedText mutableCopy];
[attrStr enumerateAttributesInRange:NSMakeRange(0, [attrStr length])
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
if([mutableAttributes objectForKey:NSUnderlineStyleAttributeName] != nil) {
//It's enabled for this button
}
}];
To disable button shapes for a specific button
[btn.titleLabel.attributedText addAttribute: NSUnderlineStyleAttributeName value: #(0) range: NSMakeRange(0, [attributedText length])];
I converted the code from this post to Swift (4.2):
import UIKit
public extension UIAccessibility {
public static var isButtonShapesEnabled: Bool {
let button = UIButton()
button.setTitle("Button Shapes", for: .normal)
return button.titleLabel?.attributedText?.attribute(NSAttributedString.Key.underlineStyle, at: 0, effectiveRange: nil) != nil
}
}
Usage:
if UIAccessibility.isButtonShapesEnabled {
// Apply button shapes style to custom button...
}
Tested and working in iOS 12.
Originally posted at my own question: Click
I had the same problem and I found no official solution. So the only workaround that I found until Apple releases a solution is to render a UIToolbar into an image and check if the button is underlined:
+ (BOOL)isUsesButtonShapes {
BOOL result = FALSE;
CGRect rect = CGRectMake(0, 0, 320, 44);
CGPoint point = CGPointMake(26, 33);
UIToolbar *toolbar = [[[UIToolbar alloc] initWithFrame:rect] autorelease];
toolbar.backgroundColor = [UIColor whiteColor];
toolbar.tintColor = [UIColor darkGrayColor];
toolbar.barTintColor = [UIColor whiteColor];
[toolbar setItems:#[[[[UIBarButtonItem alloc] initWithTitle:#"Test" style:UIBarButtonItemStyleBordered target:nil action:nil] autorelease]]];
toolbar.barStyle = UIBarStyleDefault;
toolbar.translucent = FALSE;
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[toolbar.layer renderInContext:context];
int bpr = CGBitmapContextGetBytesPerRow(context);
unsigned char *data = CGBitmapContextGetData(context);
if (data != NULL) {
int offset = (int) (bpr * point.y + 4 * point.x);
int blue = data[offset + 0];
result = blue < 250;
}
UIGraphicsEndImageContext();
return result;
}
It basically just renders the UIToolbar into an image:
Then it checks if there is an underline in the pixel under the "T". I know that this can easily break if Apple changes the way how the UIToolbar is rendered. But maybe this method can be improved and is at least better than nothing? Sorry, it isn't a good solution but I didn't find anything better yet.
This is only semi related, but I kind of "roll my own" for Button shapes and make the option available to the user via a settings menu.
I apologize for not being "spot on" regarding the question, but this is what I ended up with by thinking about the same question.
(The example is set up to always use a semi-circle for rounded corners regardless the size - please modify as you wish).
-(void)setBorderForButton:(UIButton*)theButton withSetting:(BOOL)theSetting{
if (theSetting == YES){
theButton.layer.cornerRadius = theButton.frame.size.height/2;
theButton.layer.borderWidth = 1;
theButton.layer.borderColor = [UIColor yourDesiredColor].CGColor;
theButton.clipsToBounds = YES;
}
}
How to change text color of UISearchBar in iOS 7?
In iOS 6, I was subclassing the UISearchBar and in layoutSubviews customising the properties of UITextField subview of UISearchBar.
But in iOS 7, UISearchBar doesn't have UITextField as its subview. How to fix this?
In iOS 7 to access Text Field you have to reiterate on level more. Change your code like this
for (UIView *subView in self.searchBar.subviews)
{
for (UIView *secondLevelSubview in subView.subviews){
if ([secondLevelSubview isKindOfClass:[UITextField class]])
{
UITextField *searchBarTextField = (UITextField *)secondLevelSubview;
//set font color here
searchBarTextField.textColor = [UIColor blackColor];
break;
}
}
}
Note : This is Not Public API
OR
You can use appearance Property of UIControls, Like
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor]}];
Note: Appearance proxy can be used for iOS 9.0+
OutPut
You can set The tintcolor to apply to key elements in the search bar.
Use tintColor to tint foreground elements.
Use barTintColor to tint the bar background.
In iOS v7.0, all subclasses of UIView derive their behavior for tintColor from the base class. See the discussion of tintColor at the UIView level for more information.
Apple Doc
You can set the text colour by
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setTextColor:[UIColor blueColor]];
For XCode 6 (iOS8 SDK) the following DOESN'T work
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setTextColor:[UIColor redColor]];
But the following DOES work (for deployment to iOS7 and iOS8)
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor]}];
While it's true that the UIAppearance protocol is a "public API," it's not true that UITextField supports this.
If you take a look at UITextField.h and look for the string "UI_APPEARANCE_SELECTOR" you'll see that it has no instances of this string. If you look at UIButton, you find quite a few - these are all of the properties that are officially supported by the UIAppearance API. It's somewhat well-known that UITextField is not supported by the UIAppearance API, so the code in Sandeep's answer will not always work and it's actually not the best approach.
This is a useful post with useful links: http://forums.xamarin.com/discussion/175/uitextfield-appearance
The correct approach is unfortunately messy - iterate through the subviews (or subviews of main subview for iOS7) and set it manually. Otherwise you will have unreliable results. But you can just create a category for UISearchBar and add a setTextColor:(UIColor*)color method. Example:
- (void)setTextColor:(UIColor*)color
{
for (UIView *v in self.subviews)
{
if([Environment isVersion7OrHigher]) //checks UIDevice#systemVersion
{
for(id subview in v.subviews)
{
if ([subview isKindOfClass:[UITextField class]])
{
((UITextField *)subview).textColor = color;
}
}
}
else
{
if ([v isKindOfClass:[UITextField class]])
{
((UITextField *)v).textColor = color;
}
}
}
}
Caution : This should lead to App Rejection!
KVC FTW. This did it for me.
UITextField *searchField = [self.searchBar valueForKey:#"_searchField"];
searchField.textColor = [UIColor redColor];
You can set the text attributes like so
[[UITextField appearanceWhenContainedIn:[<YOUR_CONTROLLER_NAME> class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont systemFontOfSize:14.0]}];
Here is working example done in C# using Xamarin:
SearchBar = new UISearchBar ();
foreach (var subView in SearchBar.Subviews) {
foreach (var field in subView.Subviews) {
if (field is UITextField) {
UITextField textField = (UITextField)field;
textField.TextColor = UIColor.White;
}
}
}
Hope this helps someone.
This seems to be the correct answer https://stackoverflow.com/a/19315895/2493073
The Swift version of it is:
UITextField.appearanceWhenContainedInInstancesOfClasses([UISearchBar.self]).textColor = UIColor.blueColor()
This would only work for iOS 9.0, in order to make it work for lower versions you'll need to follow this question. https://stackoverflow.com/a/27807417/2493073
Swift Extension
public extension UISearchBar {
public func setTextColor(color: UIColor) {
let svs = subviews.flatMap { $0.subviews }
guard let tf = (svs.filter { $0 is UITextField }).first as? UITextField else { return }
tf.textColor = color
}
}
In my case, I have multiple UISearchBar objects and they need to change the textField font color. The appearanceWhenContainedIn update one UISearchBar behavior, but another doesn't.
I subclass the UISearchBar and implement custom -(id)initWithFrame: as following, and it works.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.searchBarStyle = UISearchBarStyleMinimal;
self.tintColor = [UIColor whiteColor];
self.barTintColor = [UIColor whiteColor];
[[UITextField appearanceForTraitCollection:self.traitCollection whenContainedIn:[self class], nil] setDefaultTextAttributes:
#{
NSForegroundColorAttributeName : [UIColor whiteColor]
}];
}
return self;
}
UIAppearance Protocol Reference said that,
In other words, the containment statement in
appearanceWhenContainedIn: is treated as a partial ordering. Given a
concrete ordering (actual subview hierarchy), UIKit selects the
partial ordering that is the first unique match when reading the
actual hierarchy from the window down.
So, appearanceWhenContainedIn: won't deal with all UISearchBar in the hierachy of UIWindow. And it suggests that.
Use the appearanceForTraitCollection: and
appearanceForTraitCollection:whenContainedIn: methods to retrieve the
proxy for a class with the specified trait collection.
The easiest way to do it is by putting this code in viewDidAppear or viewWillAppear:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor whiteColor]}];
This works in iOS 8 and Xcode 6, unlike some of the other code. It can mess around with the font and text size, etc, but you can change that in the text attributes.
That changes the text colour for all search bars in your app. If you only want to change one, use the above code, and then in any other views with a search bar, use the same code but set the colour to whatever you want.
you can use search bar inside textfield
UISearchBar * searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 50, 320, 44) ];
searchBar.autocorrectionType = UITextAutocapitalizationTypeWords;
searchBar.delegate = self;
searchBar.searchBarStyle = UISearchBarStyleMinimal;
searchBar.barTintColor = [UIColor redColor];
[[UITextField appearanceWhenContainedIn:[searchBar class], nil]setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor whiteColor]}];
[self.view addSubview:searchBar];
Update in Swift 3
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).textColor = UIColor.black
Even though I would have preferred to use appearance API, it didn't work with iOS8. Here's the least hackish solution I did come with:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.shouldEnableSearchBar)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UITextField *searchBarTextField = [[AFPBaseViewController findSubviewsOfView:self.searchBar ofClass:[UITextField class]] firstObject];
searchBarTextField.textColor = AFPConstantsColorGold;
});
}
}
You could maybe even create a UIView category with this. The reason why this has to be called in viewDidAppear is that UISearchBar is actually contained in a ViewController, and doesn't load all its subviews until it has appeared on screen. It could be added into viewWillAppear too, but I haven't tested it.
+ (NSArray *)findSubviewsOfView:(UIView *)view ofClass:(Class)class
{
NSMutableArray *targetSubviews = [NSMutableArray new];
for (id subview in view.subviews)
{
if ([subview isKindOfClass:class])
{
[targetSubviews addObject:subview];
}
if ([subview subviews].count)
{
[targetSubviews addObjectsFromArray:[self findSubviewsOfView:subview ofClass:class]];
}
}
return targetSubviews.copy;
}
This is the right solution for iOS8:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSFontAttributeName: [UIFont fontWithName:#"Helvetica" size:14], NSForegroundColorAttributeName:[UIColor lightGrayColor]}];
You have to set the font as well, otherwise, the font size will be wrong.
This class will give you full control over every item in the UISearchBar
import UIKit
class SMTSearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
initialize()
}
convenience init() {
self.init(frame: CGRectZero)
initialize()
}
// Style the view
func initialize() {
// Search Area
let searchField = valueForKey("searchField") as! UITextField
searchField.textColor = Colors.White
searchField.font = UIFont(name: Fonts.MuseoSans500, size: 16)
searchField.backgroundColor = Colors.Black.colorWithAlphaComponent(0.1)
// Icons
let searchIcon = UIImage(named: Icons.Search)?.imageWithTint(Colors.White)
let smallClearIconNormal = UIImage(named: Icons.SmallClear)?.imageWithTint(Colors.White)
let smallClearIconHighLight = UIImage(named: Icons.SmallClear)?.imageWithTint(Colors.White.colorWithAlphaComponent(0.5))
setImage(searchIcon, forSearchBarIcon: .Search, state: .Normal)
setImage(smallClearIconHighLight, forSearchBarIcon: .Clear, state: .Highlighted)
setImage(smallClearIconNormal, forSearchBarIcon: .Clear, state: .Normal)
}
func setPlaceHolder(placeholder: String) {
for subView in subviews{
for subsubView in subView.subviews {
if let textField = subsubView as? UITextField {
textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSForegroundColorAttributeName: Colors.White.colorWithAlphaComponent(0.5)])
}
}
}
}
}
Usage (in navigation bar)
let searchBar:SMTSearchBar = SMTSearchBar()
searchBar.sizeToFit()
searchBar.setPlaceHolder("Search for cool things")
navigationItem.titleView = searchBar
searchBar.becomeFirstResponder()
So I have an app that behaves like a photo gallery and I'm implementing the ability for the user to delete the images. Here is the setup: I have 9 UIImageViews, imageView, imageView2, etc. I also have an "Edit" button, and a tapGesture action method. I dragged a Tap Gesture Recognizer over onto my view in IB, and attached it to each one of the UIImageViews. I also attached the tapGesture action method to each of the UIImageViews. Ideally, I would like the method to only become active when the "Edit" button is pressed. When the user taps Edit, then taps on the picture they want to delete, I would like a UIAlertView to appear, asking if they are sure they want to delete it. Here is the code I'm using:
- (IBAction)editButtonPressed:(id)sender {
editButton.hidden = YES;
backToGalleryButton.hidden = NO;
tapToDeleteLabel.hidden = NO;
}
- (IBAction)tapGesture:(UITapGestureRecognizer*)gesture
{
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
if (buttonIndex != [alertView cancelButtonIndex]) {
UIImageView *view = [self.UIImageView];
if (view) {
[self.array removeObject:view];
}
CGPoint tapLocation = [gesture locationInView: self.view];
for (UIImageView *imageView in self.view.subviews) {
if (CGRectContainsPoint(self.UIImageView.frame, tapLocation)) {
((UIImageView *)[self.view]).image =nil;
}
}
[self.user setObject:self.array forKey:#"images"];
}
}
This code is obviously riddled with errors:
"Use of undeclared identifier button index" on this line: if (buttonIndex != [alertView cancelButtonIndex])
"Property UIImageView not found on object of type PhotoViewController" on this line UIImageView *view = [self.UIImageView];
And "Expected identifier" on this line ((UIImageView *)[self.view]).image =nil;
I'm very new to programming, and I'm surprised that I even made it this far. So, I'm just trying to figure out how I need to edit my code so that the errors go away, and that it can be used whenever one of the 9 image views is tapped, and also so that this method only fires when the Edit button is pushed first. I was using tags earlier, and it worked great, but I save the images via NSData, so I can't use tags anymore. Any help is much appreciated, thanks!
First, you don't want to attach the tap gesture to the image views. Also, if you are going to have more than 9 images, you may want a scroll view, or handle scrolling separately. First, remove that gesture recognizer and all its connections.
Next, determine what type of view you will use as your gallery canvas. A simple View, or a ScrollView, or whatever... it doesn't really matter right now, just to get it working. You want to ctrl-drag that view into your interface definition, so it drops an IBOutlet for the view (that way you can reference it in code).
You will place your ImageViews onto the view I just mentioned.
You can have an internal flag for the gesture recognizer, but it also has a property that use can enable/disable it whenever you want. Thus, you can have it active/inactive fairly easily.
All you want to do is drop a single tap-gesture-recognizer onto your controller, and connect it to the implementation section of the controller. It will generate a stub for handling the recognizer. It will interpret taps "generally" for the controller, and call your code whenever a tap is made on the view.
Some more code...
Creates a frame for the "new" image in the scroll view.
- (CGRect)frameForData:(MyData*)data atIndex:(NSUInteger)idx
{
CGPoint topLeft;
int row = idx / 4;
int col = idx % 4;
topLeft.x = (col+1)*HORIZ_SPACING + THUMBNAIL_WIDTH * (col);
topLeft.y = (row+1)*VERT_SPACING + THUMBNAIL_HEIGHT * (row);
return CGRectMake(topLeft.x, topLeft.y, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
}
Creates an image view for each piece of metadata, and a small border.
- (UIImageView*)createImageViewFor:(MetaData*)metadata
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:metadata.lastImage];
imageView.frame = CGRectMake(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);;
imageView.layer.borderColor = [[UIColor blackColor] CGColor];
imageView.layer.borderWidth = 2.0;
imageView.userInteractionEnabled = YES;
return imageView;
}
This is where the views are created and added to the parent...
imageView = [self createImageViewFor:metadata];
//[data.imageView sizeToFit];
// Make sure the scrollView contentSize represents the data
CGRect lastFrame = [self frameForData:data atIndex:self.data.count-1];
CGFloat newHeight = lastFrame.origin.y + lastFrame.size.height;
if (self.bookshelfScrollView.contentSize.height < newHeight) {
CGSize newSize = self.bookshelfScrollView.contentSize;
newSize.height = newHeight;
self.bookshelfScrollView.contentSize = newSize;
}
[self.bookshelfScrollView addSubview:data.imageView];
So, you just create each frame, add them to the view, and the only thing you have to do is enable user interaction on them, because otherwise the scroll view does not allow the gesture through.
OK... Looking at the code you posted... since you didn't say what was wrong with it... hard to say... The below is your code... My comments are, well, Comments...
- (IBAction)editButtonPressed:(id)sender {
editButton.hidden = YES;
backToGalleryButton.hidden = NO;
tapToDeleteLabel.hidden = NO;
}
- (IBAction)tapGesture:(UITapGestureRecognizer*)gesture
{
// I don't think I'd do this here, but it shouldn't cause "problems."
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
// In your controller, you have the main view, which is the view
// on which you added your UIViews. You need that view. Add it as an IBOutlet
// You should know how to do that... ctrl-drag to the class INTERFACE source.
// Assuming you name it "galleryView"
// Take the tap location from the gesture, and make sure it is in the
// coordinate space of the view. Loop through all the imageViews and
// find the one that contains the point where the finger was taped.
// Then, "remove" that one from its superview...
CGPoint tapLocation = [gesture locationInView: self.galleryView];
for (UIImageView *imageView in self.galleryView.subviews) {
if (CGRectContainsPoint(imageView.frame, tapLocation)) {
[imageView removeFromSuperview];
}
}
}
Personally, in my little photo gallery, I bypassed all of the gesture recognizer stuff by replacing the UIImageView controls with UIButton controls, setting the image property for the button like you would for the UIImageView. It looks identical, but then you get the functionality of tapping on a thumbnail for free, with no gestures needed at all.
So, my view just has a UIScrollView, which which I programmatically add my images as buttons with the image set for the button control, e.g.:
- (void)loadImages
{
listOfImages = [ImageData newArrayOfImagesForGallery:nil];
// some variables to control where I put my thumbnails (which happen to be 76 x 76px)
int const imageWidth = 76;
int const imageHeight = imageWidth;
NSInteger imagesPerRow;
NSInteger imagePadding;
NSInteger cellWidth;
NSInteger cellHeight;
// some variables to keep track of where I am as I load in my images
int row = 0;
int column = 0;
int index = 0;
// add images to navigation bar
for (ImageData *item in listOfImages)
{
// figure out what row and column I'm on
imagesPerRow = self.view.frame.size.width / (imageWidth + 2);
imagePadding = (self.view.frame.size.width - imageWidth*imagesPerRow) / (imagesPerRow + 1);
cellWidth = imageWidth + imagePadding;
cellHeight = imageHeight + imagePadding;
// this is how I happen to grab my UIImage ... this will vary by implementation
UIImage *thumb = [item imageThumbnail];
// assuming I found it...
if (thumb)
{
// create my button and put my thumbnail image in it
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(column * cellWidth + imagePadding,
row * cellHeight + imagePadding,
imageWidth,
imageHeight);
[button setImage:thumb forState:UIControlStateNormal];
[button addTarget:self
action:#selector(buttonClicked:)
forControlEvents:UIControlEventTouchUpInside];
button.tag = index++;
[[button imageView] setContentMode:UIViewContentModeScaleAspectFit];
// add it to my view
[scrollView addSubview:button];
// increment my column (and if necessary row) counters, making my scrollview larger if I need to)
if (++column == imagesPerRow)
{
column = 0;
row++;
[scrollView setContentSize:CGSizeMake(self.view.frame.size.width, (row+1) * cellHeight + imagePadding)];
}
}
}
}
// I also have this in case the user changes orientation, so I'll move my images around if I need to
- (void)rearrangeImages
{
if (!listOfImages)
{
[self loadImages];
return;
}
// a few varibles to keep track of where I am
int const imageWidth = 76;
int const imagesPerRow = self.view.frame.size.width / (imageWidth + 2);
int const imageHeight = imageWidth;
int const imagePadding = (self.view.frame.size.width - imageWidth*imagesPerRow) / (imagesPerRow + 1);
int const cellWidth = imageWidth + imagePadding;
int const cellHeight = imageHeight + imagePadding;
NSArray *buttons = [[NSArray alloc] initWithArray:[scrollView subviews]];
int row;
int column;
int index;
CGRect newFrame;
// iterate through the buttons
for (UIView *button in buttons)
{
index = [button tag];
if ([button isKindOfClass:[UIButton class]] && index < [listOfImages count])
{
// figure out where the button should go
row = floor(index / imagesPerRow);
column = index % imagesPerRow;
newFrame = CGRectMake(column * cellWidth + imagePadding,
row * cellHeight + imagePadding,
imageWidth,
imageHeight);
// if we need to move it, then animation the moving
if (button.frame.origin.x != newFrame.origin.x || button.frame.origin.y != newFrame.origin.y)
{
[UIView animateWithDuration:0.33 animations:^{
[button setFrame:newFrame];
}];
}
}
}
NSInteger numberOfRows = floor(([listOfImages count] - 1) / imagesPerRow) + 1;
[scrollView setContentSize:CGSizeMake(self.view.frame.size.width, numberOfRows * cellHeight + imagePadding)];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self rearrangeImages];
}
Hopefully this gives you an idea of how you can programmatically add a UIButton to act as an image in a gallery. I've tried to edit this on the fly, so I apologize in advance if I introduced any errors in the process, but it should give you a sense of what you could conceivably do ... I removed my code to do this in a separate GCD queue (which I do because I have close to 100 images, and doing this on the main queue is too slow.)
Anyway, you can then create your buttonClicked method to do your display of your UIAlertView.
- (void)buttonClicked:(UIButton *)sender
{
if (inEditMode)
{
// create your UIAlterView using [sender tag] to know which image you tapped on
}
}
Finally, you have two buttons on your UIAlertView, but you don't seem to check to see what button the user clicked on. Your view controller should be a UIAlterViewDelegate, in which you have defined your alertView:clickedButtonAtIndex which will do your actual edit steps.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// ok, will do what I need to do with whatever the user tapped in my alertview
}
The easiest thing I can think of off the top of my head is to have the gesture disabled by default, and then have the gesture enabled once the edit button is hit. This way, the picture will only respond to the tap gesture recognizer if it is in "Edit" mode.
In my desktop Mac OS X app, I'd like to programatically create a NSTextField "label" which has the same behavior and properties as a typical label created in Interface Builder.
I usually use (and very much like) IB, but in this case it must be done programatically.
Try as I might, I can't seem to find the combination of method calls that will programatically produce the same label-y behavior as a "Label" dragged from the IB View Library palette.
Can anyone provide or point out some example code of how to do this programatically? Thx.
A label is actually an instance of NSTextField, a subclass of NSView. So, since it is a NSView, it has to be added to another view.
Here's a working code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTextField *textField;
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 200, 17)];
[textField setStringValue:#"My Label"];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[view addSubview:textField];
}
macOS 10.12 and Later
Starting with macOS 10.12 (Sierra), there are three new NSTextField constructors:
NSTextField(labelWithString:), which the header file comment says “Creates a non-wrapping, non-editable, non-selectable text field that displays text in the default system font.”
NSTextField(wrappingLabelWithString:), which the header file comment says “Creates a wrapping, non-editable, selectable text field that displays text in the default system font.”
NSTextField(labelWithAttributedString:), which the header file comment says “Creates a non-editable, non-selectable text field that displays attributed text. The line break mode of this field is determined by the attributed string's NSParagraphStyle attribute.”
I tested the ones that take a plain (non-attributed string), and they create text fields that are similar to, but not precisely the same as, the text fields created in a storyboard or xib.
The important difference is that both constructors create a text field with textBackgroundColor (normally pure white) as its background color, while the storyboard text field uses controlColor (normally about 90% white).
Unimportantly, both constructors also set their fonts by calling NSFont.systemFont(ofSize: 0) (which produces a different NSFont object than my code below, but they wrap the same underlying Core Text font).
The wrappingLabelWithString: constructor sets the field's isSelectable to true. (This is documented in the header file.)
macOS 10.11 and Earlier
I compared four NSTextField instances: one created by dragging a “Label” to a storyboard, another created by dragging a “Wrapping Label” to a storyboard, and two in code. Then I carefully modified properties of the code-created labels until all their properties were exactly the same as the storyboard-created labels. These two methods are the result:
extension NSTextField {
/// Return an `NSTextField` configured exactly like one created by dragging a “Label” into a storyboard.
class func newLabel() -> NSTextField {
let label = NSTextField()
label.isEditable = false
label.isSelectable = false
label.textColor = .labelColor
label.backgroundColor = .controlColor
label.drawsBackground = false
label.isBezeled = false
label.alignment = .natural
label.font = NSFont.systemFont(ofSize: NSFont.systemFontSize(for: label.controlSize))
label.lineBreakMode = .byClipping
label.cell?.isScrollable = true
label.cell?.wraps = false
return label
}
/// Return an `NSTextField` configured exactly like one created by dragging a “Wrapping Label” into a storyboard.
class func newWrappingLabel() -> NSTextField {
let label = newLabel()
label.lineBreakMode = .byWordWrapping
label.cell?.isScrollable = false
label.cell?.wraps = true
return label
}
}
If you use one of these methods, don't forget to set your field's frame, or turn off its translatesAutoresizingMaskIntoConstraints and add constraints.
Here is the code I used to compare the different text fields, in case you want to check:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var label: NSTextField!
#IBOutlet var multilineLabel: NSTextField!
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
let codeLabel = NSTextField.newLabel()
let codeMultilineLabel = NSTextField.newWrappingLabel()
let labels = [label!, codeLabel, multilineLabel!, codeMultilineLabel]
for keyPath in [
"editable",
"selectable",
"allowsEditingTextAttributes",
"importsGraphics",
"textColor",
"preferredMaxLayoutWidth",
"backgroundColor",
"drawsBackground",
"bezeled",
"bezelStyle",
"bordered",
"enabled",
"alignment",
"font",
"lineBreakMode",
"usesSingleLineMode",
"formatter",
"baseWritingDirection",
"allowsExpansionToolTips",
"controlSize",
"highlighted",
"continuous",
"cell.opaque",
"cell.controlTint",
"cell.backgroundStyle",
"cell.interiorBackgroundStyle",
"cell.scrollable",
"cell.truncatesLastVisibleLine",
"cell.wraps",
"cell.userInterfaceLayoutDirection"
] {
Swift.print(keyPath + " " + labels.map({ ($0.value(forKeyPath: keyPath) as? NSObject)?.description ?? "nil" }).joined(separator: " "))
}
}
}
This can be tricky to get right. I don't have the recipe for an exact replica handy, but when I've been stuck in a similar situation, here's what I do:
Create a UI element in IB.
Add an outlet to it from my controller class.
Break in gdb in awakeFromNib or whatever.
From the gdb prompt, "p *whateverOutlet" ... this will show you the C struct contents of the label NSTextField that IB set up.
By looking at all the myriad values in there, you can get a lot of guesses about what you're neglecting to set. Usually it ends up being some magic combination of bezel and border settings, that gets you where you want to be.
You could try using nib2objc to get all the properties that IB sets
Disassembled AppKit in Objective-C:
BOOL TMPSierraOrLater() {
static BOOL result = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
result = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 10, 12, 0 }];
});
return result;
}
#implementation NSTextField (TMP)
+ (instancetype)TMP_labelWithString:(NSString *)stringValue {
if (TMPSierraOrLater()) {
return [self labelWithString:stringValue];
}
NSParameterAssert(stringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
label.lineBreakMode = NSLineBreakByClipping;
label.selectable = NO;
[label setContentHuggingPriority:(NSLayoutPriorityDefaultLow + 1) forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.stringValue = stringValue;
[label sizeToFit];
return label;
}
+ (instancetype)TMP_wrappingLabelWithString:(NSString *)stringValue {
if (TMPSierraOrLater()) {
return [self wrappingLabelWithString:stringValue];
}
NSParameterAssert(stringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
label.lineBreakMode = NSLineBreakByWordWrapping;
label.selectable = YES;
[label setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.stringValue = stringValue;
label.preferredMaxLayoutWidth = 0;
[label sizeToFit];
return label;
}
+ (instancetype)TMP_labelWithAttributedString:(NSAttributedString *)attributedStringValue {
if (CRKSierraOrLater()) {
return [self labelWithAttributedString:attributedStringValue];
}
NSParameterAssert(attributedStringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
[label setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.attributedStringValue = attributedStringValue;
[label sizeToFit];
return label;
}
#pragma mark - Private API
+ (instancetype)TMP_newBaseLabelWithoutTitle {
NSTextField *label = [[self alloc] initWithFrame:CGRectZero];
label.textColor = NSColor.labelColor;
label.font = [NSFont systemFontOfSize:0.0];
label.alignment = NSTextAlignmentNatural;
label.baseWritingDirection = NSWritingDirectionNatural;
label.userInterfaceLayoutDirection = NSApp.userInterfaceLayoutDirection;
label.enabled = YES;
label.bezeled = NO;
label.bordered = NO;
label.drawsBackground = NO;
label.continuous = NO;
label.editable = NO;
return label;
}
#end
Specifically, you will want to setBordered:NO, and set the bezel style to whatever that bezel style is which I forgot. Also setEditable:NO, and optionally setSelectable:NO. That should suffice.