Does anybody knows how to get the UITextField from any UISearchBar on iOS 7.1, i need to change the font and search icon but the old code doesn´t work with the new version 7.1, Thanks for your help.
This is the old code to get the UITextField, this only works on versions less than 7.1
UITextField *txfSearchField = [default_search_bar valueForKeyPath:#"_searchField"];
If you are in subclass of UISearchBar you can use simple fix.
Somehow after init there are not instances of UITextField in subviews.
But in - (void)layoutSubviews you can find it.
So the solution is to move code, that search for textfield out of init.
You can use this simple method, that will work on all iOS prior to 7.1
- (UIView *)firstViewOfClass:(Class)class inSubviewsOfView:(UIView *)view
{
UIView *result = nil;
for (UIView *subview in view.subviews)
{
if ([subview isKindOfClass:class])
{
result = subview;
}
else
{
result = [self firstViewOfClass:class inSubviewsOfView:subview];
}
if (result)
{
break;
}
}
return result;
}
Example of usage:
- (void)layoutSubviews
{
if (!self.textField)
{
self.textField = (UITextField *)[self firstViewOfClass:[UITextField class] inSubviewsOfView:self];
}
[super layoutSubviews];
// your code here.
}
You should try to search for it like this:
for (UIView* subView in self.searchBar.subviews) {
for (UIView* searchView in subView.subviews) {
if ([searchView isKindOfClass:[UITextField class]]) {
self.searchTextField = (UITextField*)searchView;
break;
}
}
}
Related
I'm looking to do a simple one colour search bar like this:
I've succeeded in doing this in my view controller by setting this image as a background image:
and with this code:
[self.searchBar setTranslucent:NO];
[self.searchBar setSearchFieldBackgroundImage:[UIImage imageNamed:#"searchbg.png"] forState:UIControlStateNormal];
However when I click on the search bar and it goes into the searchdisplaycontroller, it looks like this:
There's a white section there that I can't get rid off. I've tried doing this:
[self.searchDisplayController.searchBar setSearchFieldBackgroundImage:[UIImage imageNamed:#"searchbg.png"] forState:UIControlStateNormal];
But it doesn't seem to work. I'm a bit puzzled as to why they act differently since they're both uisearchbars?
Thanks.
Try setting borderColor and borderWidth for UISearchBar layer it will fix your color change
remember that you have to keep in mind that for ios7 and later view position is different so use as i mentioned below. also i have subclass for UISearchBar, if u want u too create one or replace self by your serachBar
- (void)setup
{
self.placeholder = #"Search Products";
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
self.backgroundColor = [UIColor lightGrayColor];
UIView *topView = self.subviews[0];
[self searchBarCustomization:topView.subviews];
} else {
self.backgroundImage = [UIImage imageWithColor:[UIColor lightGrayColor]];
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 0.0;
[self searchBarCustomization:self.subviews];
}
}
- (void)searchBarCustomization:(NSArray *)subViews
{
for (UIView *subView in subViews) {
if ([subView isKindOfClass:[UITextField class]]) {
[self customSearchBarField:subView];
}
if ([subView isKindOfClass:[UIButton class]]) {
[self customSearchCancelButton:subView];
}
}
}
- (void)customSearchBarField:(id)searchBarField
{
UITextField *searchField = (UITextField *) searchBarField;
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")) {
searchField.background = [UIImage imageWithColor:[UIColor whiteColor]];
searchField.borderStyle = UITextBorderStyleNone;
searchField.layer.cornerRadius = 5.0;
}
}
I am using Xcode and after updating to iOS 8, the shouldAutorotate function doesn't work.
I don't want my viewcontroller to autorotate.
How can I restrict autorotation in iOS 8 from Xcode?
Follow these steps:
First, in info.plist select the orientations which your application supports. Meaning if your application only displays in portrait mode then select portrait as your only setting.
If you want to restrict this orientation from appdelegate you can add this code
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
NSUInteger orientations;
UIViewController* presented = [[[[NavigationManager sharedManager ] navigationController] viewControllers] lastObject];
orientations = [presented supportedInterfaceOrientations];
return orientations;
}
Make common class for UIViewcontroller and add these methods:
BaseViewController.h
#interface BaseViewController : UIViewController{
}
BaseViewController.m
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Add the following code in Navigation controller class
-(BOOL)shouldAutorotate
{
return NO;
}
-(NSUInteger)supportedInterfaceOrientation
{
return UIInterfaceOrientationPortrait;
}
I'm making a little server app for OS X and I'm using an NSTextView to log some info about connected clients.
Whenever I need to log something I'm appending the new message to the text of the NSTextView this way:
- (void)logMessage:(NSString *)message
{
if (message) {
self.textView.string = [self.textView.string stringByAppendingFormat:#"%#\n",message];
}
}
After this I'd like the NSTextField (or maybe I should say the NSClipView that contains it) to scroll down to show the last line of its text (obviously it should scroll only if the last line is not visible yet, in fact if then new line is the first line I log it is already on the screen so there is no need to scroll down).
How can I do that programmatically?
Found solution:
- (void)logMessage:(NSString *)message
{
if (message) {
[self appendMessage:message];
}
}
- (void)appendMessage:(NSString *)message
{
NSString *messageWithNewLine = [message stringByAppendingString:#"\n"];
// Smart Scrolling
BOOL scroll = (NSMaxY(self.textView.visibleRect) == NSMaxY(self.textView.bounds));
// Append string to textview
[self.textView.textStorage appendAttributedString:[[NSAttributedString alloc]initWithString:messageWithNewLine]];
if (scroll) // Scroll to end of the textview contents
[self.textView scrollRangeToVisible: NSMakeRange(self.textView.string.length, 0)];
}
As of OS 10.6 it's as simple as nsTextView.scrollToEndOfDocument(self).
Swift 4 + 5
let smartScroll = self.textView.visibleRect.maxY == self.textView.bounds.maxY
self.textView.textStorage?.append("new text")
if smartScroll{
self.textView.scrollToEndOfDocument(self)
}
I've been messing with this for a while, because I couldn't get it to work reliably. I've finally gotten my code working, so I'd like to post it as a reply.
My solution allows you to scroll manually, while output is being added to the view. As soon as you scroll to the absolute bottom of the NSTextView, the automatic scrolling will resume (if enabled, that is).
First a category to #import this only when needed...
FSScrollToBottomExtensions.h:
#interface NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom;
- (BOOL)isAtBottom;
- (void)scrollToBottom;
#end
FSScrollToBottomExtensions.m:
#implementation NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom
{
NSRect visRect;
NSRect boundsRect;
visRect = [self visibleRect];
boundsRect = [self bounds];
return(NSMaxY(visRect) - NSMaxY(boundsRect));
}
// Apple's suggestion did not work for me.
- (BOOL)isAtBottom
{
return([self distanceToBottom] == 0.0);
}
// The scrollToBottom method provided by Apple seems unreliable, so I wrote this one
- (void)scrollToBottom
{
NSPoint pt;
id scrollView;
id clipView;
pt.x = 0;
pt.y = 100000000000.0;
scrollView = [self enclosingScrollView];
clipView = [scrollView contentView];
pt = [clipView constrainScrollPoint:pt];
[clipView scrollToPoint:pt];
[scrollView reflectScrolledClipView:clipView];
}
#end
... create yourself an "OutputView", which is a subclass of NSTextView:
FSOutputView.h:
#interface FSOutputView : NSTextView
{
BOOL scrollToBottomPending;
}
FSOutputView.m:
#implementation FSOutputView
- (id)setup
{
...
return(self);
}
- (id)initWithCoder:(NSCoder *)aCoder
{
return([[super initWithCoder:aCoder] setup]);
}
- (id)initWithFrame:(NSRect)aFrame textContainer:(NSTextContainer *)aTextContainer
{
return([[super initWithFrame:aFrame textContainer:aTextContainer] setup]);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
NSNotificationCenter *notificationCenter;
NSView *view;
// viewBoundsDidChange catches scrolling that happens when the caret
// moves, and scrolling caused by pressing the scrollbar arrows.
view = [self superview];
[notificationCenter addObserver:self
selector:#selector(viewBoundsDidChangeNotification:)
name:NSViewBoundsDidChangeNotification object:view];
[view setPostsBoundsChangedNotifications:YES];
// viewFrameDidChange catches scrolling that happens because text
// is inserted or deleted.
// it also catches situations, where window resizing causes changes.
[notificationCenter addObserver:self
selector:#selector(viewFrameDidChangeNotification:)
name:NSViewFrameDidChangeNotification object:self];
[self setPostsFrameChangedNotifications:YES];
}
- (void)handleScrollToBottom
{
if(scrollToBottomPending)
{
scrollToBottomPending = NO;
[self scrollToBottom];
}
}
- (void)viewBoundsDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)viewFrameDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)outputAttributedString:(NSAttributedString *)aAttributedString
flags:(int)aFlags
{
NSRange range;
BOOL wasAtBottom;
if(aAttributedString)
{
wasAtBottom = [self isAtBottom];
range = [self selectedRange];
if(aFlags & FSAppendString)
{
range = NSMakeRange([[self textStorage] length], 0);
}
if([self shouldChangeTextInRange:range
replacementString:[aAttributedString string]])
{
[[self textStorage] beginEditing];
[[self textStorage] replaceCharactersInRange:range
withAttributedString:aAttributedString];
[[self textStorage] endEditing];
}
range.location += [aAttributedString length];
range.length = 0;
if(!(aFlags & FSAppendString))
{
[self setSelectedRange:range];
}
if(wasAtBottom || (aFlags & FSForceScroll))
{
scrollToBottomPending = YES;
}
}
}
#end
... You can add a few more convenience methods to this class (I've stripped it down), so that you can output a formatted string.
- (void)outputString:(NSString *)aFormatString arguments:(va_list)aArguments attributeKey:(NSString *)aKey flags:(int)aFlags
{
NSMutableAttributedString *str;
str = [... generate attributed string from parameters ...];
[self outputAttributedString:str flags:aFlags];
}
- (void)outputLineWithFormat:(NSString *)aFormatString, ...
{
va_list args;
va_start(args, aFormatString);
[self outputString:aFormatString arguments:args attributeKey:NULL flags:FSAddNewLine];
va_end(args);
}
I have some customised NSTextView and custom input method so my option was to use:
self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height))
When updating my app to iOS6 standard the portrait / landscape is gone. Ir worked perfectly when I was building with Xcode 3. But now using latest Xcode and latest SDK the rotation is gone and it is always in portrait mode. No matter what I put in "Supported interface Orientations". And the code I used to get rotation before seems to have no effect at all.
I had these lines.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
return YES;
default:
return NO;
}
}
How do I change and what do I change to get it work again?
First of all, in AppDelegate, write this. THIS IS VERY IMP
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return (UIInterfaceOrientationMaskAll);
}
Then, For UIViewControllers, in which you need only PORTRAIT mode, write these functions
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskPortrait);
}
For UIViewControllers, which require LANDSCAPE too, change masking to All.
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskAllButUpsideDown);
//OR return (UIInterfaceOrientationMaskAll);
}
Now, if you want to do some changes when Orientation changes, then use this function.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
}
EDIT :
A lot depends on with which controller is your UIViewController embedded in.
Eg, If its inside UINavigationController, then you might need to subclass that UINavigationController to override orientation methods like this.
subclassed UINavigationController (the top viewcontroller of the hierarchy will take control of the orientation.) did set it as self.window.rootViewController.
- (BOOL)shouldAutorotate
{
return self.topViewController.shouldAutorotate;
}
- (NSUInteger)supportedInterfaceOrientations
{
return self.topViewController.supportedInterfaceOrientations;
}
From iOS 6, it is given that UINavigationController won't ask its UIVIewControllers for orientation support. Hence we would need to subclass it.
How to support one or more landscape controllers in app that is portrait mainly in ios6:
1) in AppDelegate
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
UINavigationController* ns = (UINavigationController*)self.window.rootViewController;
if (ns) {
UIViewController* vc = [ns visibleViewController];
//by this UIViewController that needs landscape is identified
if ([vc respondsToSelector:#selector(needIos6Landscape)])
return [vc supportedInterfaceOrientations];
}
return UIInterfaceOrientationMaskPortrait; //return default value
}
2) in UIView controller(s) that needs landscape (or portrait+lanscape etc):
//flag method
-(void)needIos6Landscape {
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
3) in controllers, to which you can RETURN from controllers, that can be rotated in landscape - this is important, otherwise they remaind landscape on return from landscape-enabled VC.
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
4) (maybe not needed, but for sure..) - subclass navigation controller(s) you using, and add:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
UIViewController* vc = [self visibleViewController];
if (vc) {
if ([vc respondsToSelector:#selector(needIos6Landscape)]) {
return [vc supportedInterfaceOrientations];
}
}
return UIInterfaceOrientationMaskPortrait;
}
The important step is to ask for orientation only controllers from your app, because during transition between controllers, for some time there is some system controller as root, and will return incorrect value (this took me 2 hrs to find out, it was reason it was not working).
Don't know whether your issue was alike but with me, the status bar was oriented correctly (landscape) and the UIViewController was portrayed.
I changed following line in the application delegate application:didFinishLaunchingWithOptions:
//[window addSubview:navigationController.view];
self.window.rootViewController = navigationController;
Apple=> this costed me a day and a half to find out, and a lot of money!!!
UIBarButtonItem does not extend UIView, so there is nothing like a frame property.
But is there any way I can get what is it's CGRect frame, relative to the application UIWindow?
Do you like to use private APIs? If yes,
UIView* view = thatItem.view;
return [view convertRect:view.bounds toView:nil];
Of course no one wants this when targeting the AppStore. A more unreliable method, and also uses undocumented features, but will pass Apple's test, is to loop through the subviews to look for the corresponding button item.
NSMutableArray* buttons = [[NSMutableArray alloc] init];
for (UIControl* btn in theToolbarOrNavbar.subviews)
if ([btn isKindOfClass:[UIControl class]])
[buttons addObject:btn];
UIView* view = [buttons objectAtIndex:index];
[buttons release];
return [view convertRect:view.bounds toView:nil];
The index is the index to your bar item in the array of .items, after removing all blank items. This assumes the buttons are arranged in increasing order, which may not be. A more reliable method is to sort the buttons array in increasing .origin.x value. Of course this still assumes the bar button item must inherit the UIControl class, and are direct subviews of the toolbar/nav-bar, which again may not be.
As you can see, there are a lot of uncertainty when dealing with undocumented features. However, you just want to pop up something under the finger right? The UIBarButtonItem's .action can be a selector of the form:
-(void)buttonClicked:(UIBarButtonItem*)sender event:(UIEvent*)event;
note the event argument — you can obtain the position of touch with
[[event.allTouches anyObject] locationInView:theWindow]
or the button view with
[[event.allTouches anyObject] view]
Therefore, there's no need to iterate the subviews or use undocumented features for what you want to do.
I didn't see this option posted (which in my opinion is much simpler), so here it is:
UIView *barButtonView = [barButtonItem valueForKey:#"view"];
In iOS 3.2, there's a much easier way to show an Action Sheet popover from a toolbar button. Merely do something like this:
- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
UIActionSheet *popupSheet;
// Prepare your action sheet
[popupSheet showFromBarButtonItem:sender animated:YES];
}
This is the implementation I use for my WEPopover project: (https://github.com/werner77/WEPopover):
#implementation UIBarButtonItem(WEPopover)
- (CGRect)frameInView:(UIView *)v {
UIView *theView = self.customView;
if (!theView.superview && [self respondsToSelector:#selector(view)]) {
theView = [self performSelector:#selector(view)];
}
UIView *parentView = theView.superview;
NSArray *subviews = parentView.subviews;
NSUInteger indexOfView = [subviews indexOfObject:theView];
NSUInteger subviewCount = subviews.count;
if (subviewCount > 0 && indexOfView != NSNotFound) {
UIView *button = [parentView.subviews objectAtIndex:indexOfView];
return [button convertRect:button.bounds toView:v];
} else {
return CGRectZero;
}
}
#end
As long as UIBarButtonItem (and UITabBarItem) does not inherit from UIView—for historical reasons UIBarItem inherits from NSObject—this craziness continues (as of this writing, iOS 8.2 and counting ... )
The best answer in this thread is obviously #KennyTM's. Don't be silly and use the private API to find the view.
Here's a oneline Swift solution to get an origin.x sorted array (like Kenny's answer suggests):
let buttonFrames = myToolbar.subviews.filter({
$0 is UIControl
}).sorted({
$0.frame.origin.x < $1.frame.origin.x
}).map({
$0.convertRect($0.bounds, toView:nil)
})
The array is now origin.x sorted with the UIBarButtonItem frames.
(If you feel the need to read more about other people's struggles with UIBarButtonItem, I recommend Ash Furrow's blog post from 2012: Exploring UIBarButtonItem)
I was able to get Werner Altewischer's WEpopover to work by passing up the toolbar along with the
UIBarButton:
Mod is in WEPopoverController.m
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item toolBar:(UIToolbar *)toolBar
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
animated:(BOOL)animated
{
self.currentUIControl = nil;
self.currentView = nil;
self.currentBarButtonItem = item;
self.currentArrowDirections = arrowDirections;
self.currentToolBar = toolBar;
UIView *v = [self keyView];
UIButton *button = nil;
for (UIView *subview in toolBar.subviews)
{
if ([[subview class].description isEqualToString:#"UIToolbarButton"])
{
for (id target in [(UIButton *)subview allTargets])
{
if (target == item)
{
button = (UIButton *)subview;
break;
}
}
if (button != nil) break;
}
}
CGRect rect = [button.superview convertRect:button.frame toView:v];
[self presentPopoverFromRect:rect inView:v permittedArrowDirections:arrowDirections animated:animated];
}
-(CGRect) getBarItemRc :(UIBarButtonItem *)item{
UIView *view = [item valueForKey:#"view"];
return [view frame];
}
You can get it from the UINavigationBar view. The navigationBar is a UIView which has 2 or 3 custom subviews for the parts on the bar.
If you know that the UIBarButtonItem is currently shown in the navbar on the right, you can get its frame from navbar's subviews array.
First you need the navigationBar which you can get from the navigationController which you can get from the UIViewController. Then find the right most subview:
UINavigationBar* navbar = curViewController.navigationController.navigationBar;
UIView* rightView = nil;
for (UIView* v in navbar.subviews) {
if (rightView==nil) {
rightView = v;
} else if (v.frame.origin.x > rightView.frame.origin.x) {
rightView = v; // this view is further right
}
}
// at this point rightView contains the right most subview of the navbar
I haven't compiled this code so YMMV.
This is not the best solution and from some point of view it's not right solution and we can't do like follow because we access to object inside UIBarBattonItem implicitly, but you can try to do something like:
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[button setImage:[UIImage imageNamed:#"Menu_Icon"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(didPressitem) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:button];
self.navigationItem.rightBarButtonItem = item;
CGPoint point = [self.view convertPoint:button.center fromView:(UIView *)self.navigationItem.rightBarButtonItem];
//this is like view because we use UIButton like "base" obj for
//UIBarButtonItem, but u should note that UIBarButtonItem base class
//is NSObject class not UIView class, for hiding warning we implicity
//cast UIBarButtonItem created with UIButton to UIView
NSLog(#"point %#", NSStringFromCGPoint(point));
as result i got next:
point {289, 22}
Before implement this code, be sure to call [window makeKeyAndVisible] in your Applition delegate application:didFinishLaunchingWithOptions: method!
- (void) someMethod
{
CGRect rect = [barButtonItem convertRect:barButtonItem.customview.bounds toView:[self keyView]];
}
- (UIView *)keyView {
UIWindow *w = [[UIApplication sharedApplication] keyWindow];
if (w.subviews.count > 0) {
return [w.subviews objectAtIndex:0];
} else {
return w;
}
}
I handled it as follows:
- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
UIView* view = [sender valueForKey:#"view"]; //use KVO to return the view
CGRect rect = [view convertRect:view.bounds toView:self.view];
//do stuff with the rect
}