How can I move the clear button in a UITextField? - iphone

For some reason, when I add a UITextfield as a subview of the contentview of a tablecell, the clearbutton does not align with the text typed in the field, and appears a bit underneath it. Is there any way I can move the text of the clearbutton to stop this from happening? Thanks for any help,

As stated by #Luda the correct way is to subclass UITextField and override - (CGRect)clearButtonRectForBounds:(CGRect)bounds. However the bounds passed in to the method are those of the view itself and not the button. Therefore you should call super to get the OS provided size (to avoid distortion of the image) and then adjust the origin to suit your needs.
e.g.
- (CGRect)clearButtonRectForBounds:(CGRect)bounds {
CGRect originalRect = [super clearButtonRectForBounds:bounds];
return CGRectOffset(originalRect, -10, 0); //shift the button 10 points to the left
}
Apple docs state:
Discussion You should not call this method directly. If you want to
place the clear button in a different location, you can override this
method and return the new rectangle. Your method should call the super
implementation and modify the returned rectangle’s origin only.
Changing the size of the clear button may cause unnecessary distortion
of the button image.

The answer from Van Du Tran in Swift 4:
class CustomTextField: UITextField {
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
let originalRect = super.clearButtonRect(forBounds: bounds)
return originalRect.offsetBy(dx: -8, dy: 0)
}
}

Swift 4, 5
Subclass UITextField (Working Perfectly, Tested)
class textFieldWithCrossButtonAdjusted: UITextField {
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
let originalRect = super.clearButtonRect(forBounds: bounds)
//move 10 points left
return originalRect.offsetBy(dx: -10, dy: 0)
}
}

I had subclassed the UITextField and override the function clearButtonRectForBounds:.
.h
#import <UIKit/UIKit.h>
#interface TVUITextFieldWithClearButton : UITextField
#end
.m
#import "TVUITextFieldWithClearButton.h"
#implementation TVUITextFieldWithClearButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)awakeFromNib
{
self.clearButtonMode = UITextFieldViewModeWhileEditing;
}
- (CGRect)clearButtonRectForBounds:(CGRect)bounds
{
return CGRectMake(bounds.size.width/2-20 , bounds.origin.y-3, bounds.size.width, bounds.size.height);
}
#end

I haven't seen this, and a screen shot would be helpful. But, the quick answer is that you can inspect the subviews array of the UITextField, find the subview that contains the clear button, and adjust its frame.origin.
Edit: It seems I've been downvoted for this answer (written in 2010). This is of not an "officially" approved method because you're manipulating private objects, but it's not detectable by Apple. The main risk is that the view hierarchy might be changed at some point.

Subclass UITextField and override this method:
- (CGRect)clearButtonRectForBounds:(CGRect)bounds
{
return CGRectMake(bounds.origin.x - 10, bounds.origin.y, bounds.size.width, bounds.size.height);
}
return the CGRect that matches your needs.

Swift 4 version would be
import UIKit
class LoginTextField: UITextField {
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: xPos, y:yPos, width: yourWidth, height: yourHeight)
}
}

Related

How to remove the space between UISlider and Minimum Image

I would like to remove the space / offset at the beginning of the UISlider and join it with the custom minimum image that I have.
Is it possible to do it programmatically in Swift?
I have checked this, but it's old and it's for Objective-C.
well, I looked at the answer you tagged, and made something up for you on playground. Not sure if it works precisely for your case, but the key is to look into the methods of UISlider that you can override here
import UIKit
class SomeController: UIViewController {
let slider = MyCustomSlider()
//etc...
}
class MyCustomSlider: UISlider {
override func maximumValueImageRect(forBounds bounds: CGRect) -> CGRect {
var r = super.maximumValueImageRect(forBounds: bounds)
r.origin.x -= 3
return r
}
}

How to remove the line under the UISearchController on iOS 11?

How can I remove the line under the UISearchController on iOS 11?
I've added the UISearchController using this code:
navigationItem.searchController = searchController
but after doing that there is a weird line under it:
Any advice on how to remove the line or at least choose its color would be greatly appreciated.
Here is another solution, a weak one but it could be useful in specific use-case.
extension UISearchController {
var hairlineView: UIView? {
guard let barBackgroundView = self.searchBar.superview?.subviews.filter({ String(describing: type(of: $0)) == "_UIBarBackground" }).first
else { return nil }
return barBackgroundView.subviews.filter({ $0.bounds.height == 1 / self.traitCollection.displayScale }).first
}
}
With that extension, you only have to write the following code in the viewWillLayoutSubviews method of your view controller:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Remove hairline from the searchBar.
self.navigationItem.searchController?.hairlineView?.isHidden = true
}
Important:
Please note that this solution is really weak and can break with future iOS updates.
Furthermore, $0.bounds.height == 1 / self.traitCollection.displayScale is unsafe and I advise you to use a proper float comparison method.
A hacky solution, but the best I have for now, is adding a white line view overlapping the dark line:
let lineView = UIView(frame: CGRect(x: 0, y: searchController.searchBar.frame.height-4, width: view.bounds.width, height: 1))
lineView.backgroundColor = .white
searchController.searchBar.addSubview(lineView)
If you added it as a subview, you will found a glitch when press back or go forward to another page. This could improve your solution #budidino
let lineView = UIView(frame: .zero)
lineView.backgroundColor = .white
searchController.searchBar.addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
lineView.leadingAnchor.constraint(equalTo: searchController.searchBar.leadingAnchor).isActive = true
lineView.trailingAnchor.constraint(equalTo: searchController.searchBar.trailingAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: searchController.searchBar.bottomAnchor, constant: 1).isActive = true
lineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
in iOS11
#interface UISearchController (Additions)
- (void)hideHairLineView;
#end
#implementation UISearchController (Additions)
- (void)hideHairLineView{
UIView *barBackgroundView = self.searchBar.superview.subviews.firstObject;
for(UIView *v in barBackgroundView.subviews) {
if ([v isKindOfClass:[UIImageView class]]) {
UIImageView *imgView= (UIImageView *)v;
if (imgView.frame.size.height <= 1.0) {
[imgView setHidden:YES];
}
}
}
}
In viewWillLayoutSubviews
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self.navigationItem.searchController hideHairLineView];
}
in iOS13
from viewDidLoad add
[self.navigationController setDelegate:self];
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController.navigationController.navigationBar.viewForLastBaselineLayout setBackgroundColor:[UIColor clearColor]];
}

NSButton with round corners and background color

I want a simple push button (the one with the round corners), and to add background to it.
I've tried 2 things:
1 - using a round button image: this is working good, until I need to scale the button, which cause the round parts to look ugly.
2 - extending the button and add color to it - but then I have trouble when I click the button - I want the "pushed" state to be at the same color as the "regular" state, but it's not the case.
this is the code I'm using to extend the button:
override func drawRect(dirtyRect: NSRect)
{
if let bgColor = bgColor {
self.layer?.cornerRadius = 4
self.layer?.masksToBounds = true
self.layer?.backgroundColor = bgColor.CGColor
bgColor.setFill()
NSRectFill(dirtyRect)
}
super.drawRect(dirtyRect)
}
Anyway, neither approach 1 nor 2 worked, so how can I achieve this ?
Just a simple button.. :(
EDIT:
I'm asking about OSX
I made a button and succeeded. It looks like this:
Code:
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
if let bgColor = bgColor {
self.layer?.cornerRadius = 4
self.layer?.masksToBounds = true
self.layer?.backgroundColor = bgColor.cgColor
bgColor.setFill()
NSRectFill(dirtyRect)
}
}
drawRect is replaced by draw, and CGColor is replaced by cgColor. The difference between yours and mine is the order. You called super.draw(dirtyRect) as last, and I called it first. Maybe your button looks like this:
I hope this can solve your problem.
Override NSButtonCell's drawWithFrame method:
func drawWithFrame(cellFrame: NSRect, inView controlView: NSView) {
var border = NSBezierPath(roundedRect: NSInsetRect(cellFrame, 0.5, 0.5), xRadius: 3, yRadius: 3)
NSColor.greenColor().set()
border.fill()
var style = NSParagraphStyle.defaultParagraphStyle()
style.alignment = NSCenterTextAlignment
var attr = [NSParagraphStyleAttributeName : style, NSForegroundColorAttributeName : NSColor.whiteColor()]
self.title.drawInRect(cellFrame, withAttributes: attr)
}
OBJECTIVE-C solution (might help some guys stumble upon this thread)
This sublass extends IB - so you can IB control either:
Background Color
Background Color on Hover
Title Color
Title Color on Hover
Corner Radius
It also includes a little alpha animation on hover.
New controls in IB (click to see screenshot)
//
// SHFlatButton.h
//
// Created by SH on 03.12.16.
// Copyright © 2016 SH. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#interface SHFlatButton : NSButton
#property (nonatomic, strong) IBInspectable NSColor *BGColor;
#property (nonatomic, strong) IBInspectable NSColor *TextColor;
#property (nonatomic, strong) IBInspectable NSColor *BGColorHover;
#property (nonatomic, strong) IBInspectable NSColor *TextColorHover;
#property (nonatomic) IBInspectable CGFloat CornerRadius;
#property (strong) NSCursor *cursor;
#end
And the implementation...
//
// SHFlatButton.m
//
// Created by SH on 03.12.16.
// Copyright © 2016 SH. All rights reserved.
//
#import "SHFlatButton.h"
#implementation SHFlatButton
- (void)awakeFromNib
{
if (self.TextColor)
[self setAttributedTitle:[self textColor:self.TextColor]];
if (self.CornerRadius)
{
[self setWantsLayer:YES];
self.layer.masksToBounds = TRUE;
self.layer.cornerRadius = self.CornerRadius;
}
}
- (void)drawRect:(NSRect)dirtyRect
{
if (self.BGColor)
{
[self.BGColor setFill];
NSRectFill(dirtyRect);
}
[super drawRect:dirtyRect];
}
- (void)resetCursorRects
{
if (self.cursor) {
[self addCursorRect:[self bounds] cursor: self.cursor];
} else {
[super resetCursorRects];
}
}
- (void)updateTrackingAreas {
NSTrackingArea* trackingArea = [[NSTrackingArea alloc]
initWithRect:[self bounds]
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways
owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void)mouseEntered:(NSEvent *)theEvent{
if ([self isEnabled]) {
[[self animator]setAlphaValue:0.9];
if (self.BGColorHover)
[[self cell] setBackgroundColor:self.BGColorHover];
if (self.TextColorHover)
[self setAttributedTitle:[self textColor:self.TextColorHover]];
}
}
- (void)mouseExited:(NSEvent *)theEvent{
if ([self isEnabled]) {
[[self animator]setAlphaValue:1];
if (self.BGColor)
[[self cell] setBackgroundColor:self.BGColor];
if (self.TextColor)
[self setAttributedTitle:[self textColor:self.TextColor]];
}
}
- (NSAttributedString*)textColor:(NSColor*)color
{
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setAlignment:NSCenterTextAlignment];
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
color, NSForegroundColorAttributeName,
self.font, NSFontAttributeName,
style, NSParagraphStyleAttributeName, nil];
NSAttributedString *attrString = [[NSAttributedString alloc]initWithString:self.title attributes:attrsDictionary];
return attrString;
}
EDIT: Button MUST have border disabled!
just try to add this two lines of code into your function.I think you are getting problem in border width.
override func drawRect(dirtyRect: NSRect)
{
if let bgColor = bgColor {
self.layer?.cornerRadius = 4
self.layer?.masksToBounds = true
self.layer?.backgroundColor = bgColor.CGColor
self.layer?.borderColor = UIColor.blackcolor()
self.layer?.borderWidth = 2.0
bgColor.setFill()
NSRectFill(dirtyRect)
}
super.drawRect(dirtyRect)
}
You need to override function layoutSubviews,i.e.
override func layoutSubviews() {
self.layer.cornerRadius = self.frame.height/2
self.layer.borderColor = UIColor.blue.cgColor
self.layer.borderWidth = 2
}
I have committed a sample project here. All your related code has been written in button.swift and it is in swift 3. Also, I have not set the background image for the button, I am leaving that task up to you.
P.S.: The answer below is for UIButton, not NSButton. Probably some of it will be valid, probably not everything...
If you want a button to be rounded, open up your main.storyboard, click on the button and the click on "Show the identity inspector". Under "User defined runtime attributes click on the little + sign twice. And change the code to look like this
Here's what works for me (Swift 4).
To set background color:
myButton.layer?.backgroundColor = CGColor.customSilver
To set corner radius:
myButton.layer?.cornerRadius = 3
Simple 3 line function.
#IBOutlet weak var button: UIButton!
func setButton(color: UIColor, title: String) {
button.backgroundColor = color
button.setTitle(title, forState: .Normal)
button.layer.cornerRadius = 8.0
}
and call it like such
setButton(UIColor.redColor(), title: "Hello")

How to expand the hitTest area of a UIButton without extruding it's background image?

I have set the UIButton's background image and put a title on it(I used setBackgroundImage method not setImage). Now I want to expand the hitTest area of a UIButton without extrude it's background image.
How can I do this?
A cleaner way to do this is to override pointInside.
Here's a Swift version:
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
let expandedBounds = CGRectInset(self.bounds, -15, -15)
return CGRectContainsPoint(expandedBounds, point)
}
Here's a corrected version of the accepted answer. We're using bounds instead of frame and CGRectInset instead of CGRectMake. Cleaner and more reliable.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return CGRectContainsPoint([self expandedBounds], point) ? self : nil;
}
- (CGRect)expandedBounds {
return CGRectInset(self.bounds, -20, -20);
}
This version lets you define a minimum hit size for all UIButtons. Crucially, it also handles the case when UIButtons are hidden, which many answers neglect.
extension UIButton {
public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
// Ignore if button hidden
if self.hidden {
return nil
}
// If here, button visible so expand hit area
let hitSize = CGFloat(56.0)
let buttonSize = self.frame.size
let widthToAdd = (hitSize - buttonSize.width > 0) ? hitSize - buttonSize.width : 0
let heightToAdd = (hitSize - buttonSize.height > 0) ? hitSize - buttonSize.height : 0
let largerFrame = CGRect(x: 0-(widthToAdd/2), y: 0-(heightToAdd/2), width: buttonSize.width+widthToAdd, height: buttonSize.height+heightToAdd)
return (CGRectContainsPoint(largerFrame, point)) ? self : nil
}
}
Well, you could extend UIButton and override UIView's hitTest method:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
int expandMargin = 20;
CGRect extendedFrame = CGRectMake(0 - expandMargin , 0 - expandMargin , self.bounds.size.width + (expandMargin * 2) , self.bounds.size.height + (expandMargin * 2));
return (CGRectContainsPoint(extendedFrame , point) == 1) ? self : nil;
}
I made a library for this very purpose.
You can choose to use a category, no subclassing required:
#interface UIView (KGHitTesting)
- (void)setMinimumHitTestWidth:(CGFloat)width height:(CGFloat)height;
#end
Or you can subclass your UIView or UIButton and set the minimumHitTestWidth and/or minimumHitTestHeight. Your button hit-test area will then be represented by these 2 values.
Just like other solutions, it uses the - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event method. The method is called when iOS performs hit-testing. This blog post has a good description on how iOS hit-testing works.
https://github.com/kgaidis/KGHitTestingViews
#interface KGHitTestingButton : UIButton <KGHitTesting>
#property (nonatomic) CGFloat minimumHitTestHeight;
#property (nonatomic) CGFloat minimumHitTestWidth;
#end
You can also just subclass and use the Interface Builder without writing any code:

viewWillAppear for subviews

I have UIScrollView with multiple UIVIew subviews. I would like to update the data that is displayed by each UIView when they appear in the visible portion of the UIScrollView.
What is the callback that gets triggered? I tried viewWillAppear, but it does not seem to get called.
Thanks. :)
You have to do the calculation yourself. Implement scrollViewDidScroll: in your scroll view delegate and calculate manually which views are visible (e.g. by checking if CGRectIntersectsRect(scrollView.bounds, subview.frame) returns true.
Swift 3 solution
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewFrame = greenView.frame
let container = CGRect(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y, width: scrollView.frame.size.width, height: scrollView.frame.size.height)
// We may have received messages while this tableview is offscreen
if (viewFrame.intersects(container)) {
// Do work here
print("view is visible")
}
else{
print("nope view is not on the screen")
}
}
Above answers are correct if your scrollview is not in the zoomed in state. In case if your scrollview can zoom above calculation won't work as you need to consider zoom too
here is the code
CGRect visibleRect;
visibleRect.origin = self.mapScrollView.contentOffset;
visibleRect.size = self.mapScrollView.bounds.size;
float theScale = 1.0 / self.mapScrollView.zoomScale;
visibleRect.origin.x *= theScale;
visibleRect.origin.y *= theScale;
visibleRect.size.width *= theScale;
visibleRect.size.height *= theScale;
if(CGRectIntersectsRect(visibleRect, btnPin.frame)){
...
}
A slight refinement. I wanted to know the amount of the view that was displayed in the scrollview:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
{
// Figure out how much of the self.userFeedbackView is displayed.
CGRect frame = CGRectIntersection(self.scrollView.bounds, self.userFeedbackView.frame);
CGFloat proportion = (frame.size.height*frame.size.width)/(self.userFeedbackView.frameWidth*self.userFeedbackView.frameHeight);
NSLog(#"%f; %#", proportion, NSStringFromCGRect(frame));
}
Ole Begemann's answer in swift 5,
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.intersects(subViewFrame){
// do something
}
}
Note: Pls make sure that the subViewFrame is calculated with respect to the scrollView's frame.