How to make iOS Button give visual feedback when pressed? - ios

I am making an iOS 7 app, I know that Apple's new design guidelines call for a bunch of flat design minimalist stuff, but this app is not really targeted at the tech-savvy crowd, so apple's guidelines pose a problem. I have a regular button, with just text in it, and I would like to put an outline around it, and I would also like for the button to react to being pressed, so that people actually know it is a button, and so that they do not freak out when the button takes them to a different app? So how do I
Put an outline around a regular iOS button?
Make a regular iOS Button give some simple visual feedback to being pressed?

Simplest way: make the UIButton's type be "System", rather than "Custom". A system button's image and/or text will highlight when touched.
You should do this in Interface Builder, by changing button's "Type" to be "System"
However, if you need to do it programmatically, you can do:
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
As for the UIButton's border, you can do:
- (void)viewDidLoad {
[super viewDidLoad];
self.button.layer.cornerRadius = 5;
self.button.layer.borderColor = [UIColor blackColor];
self.button.layer.borderWidth = 1;
}

If you are using a storyboard (interface builder) for designing your app it's quite easy:
Create a subclass of UIButton. Let's call it XYBorderButton.
In XYBorderButton.m add the methods:
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self makeBorder];
}
return self;
}
- (void)makeBorder {
self.layer.cornerRadius = 10.0;
self.layer.borderColor = [UIColor blueColor];
self.layer.borderWidth = 1.0;
}
Then in interface builder select the button and change its class to XYBorderButton
You can give visual feedback for example by changing the button's background color and/or by changing its font color.
Setting these attributes is quite easy with Interface Builder:
Just select the button, then choose the state "Highlighted" in the state config dropdown menu and set the background color and font color as desired.

extension UIButton {
func provideVisualFeedback4press()
{
backgroundColor = cyan
alpha = 0
UIView .animate(withDuration: 0.1, animations: { [weak self] in
guard let s = self else {
return
}
s.alpha = 1
}, completion: { [weak self] completed in
if completed {
guard let s = self else {
return
}
s.backgroundColor = UIColor.white
}
})
}}
usage:
#objc func backAction(_ sender:UIButton!)
{
sender.provideVisualFeedback4press()

If you set the text or image properties of a UIButton, it'll automatically give feedback when pressed (the font color and image will go darker). However if you simply placed the button on top of some other controls, then you'll have to wire up to the Touch Down event and manually change the appearance to any control you want.

Related

Changing back button tint color on scroll

I am trying to find a way to change the tint color of the backBarButtonItem based on the scroll position. In the example below, the button should be yellow by default, but then at a certain threshold it should change to red.
Although using breakpoints I can see the code triggering in each block, but unfortunately backBarButtonItem never changes to red and always remains yellow. Any suggestions on why this might be the case? I'm assuming that you might not be able to change the back button in the navigation bar once it's already set.
CGFloat totalHeight = CGRectGetMaxY(self.frame);
CGFloat barHeight = CGRectGetHeight(self.frame);
CGFloat offsetHeight = (self.scrollview.contentOffset.y - self.scrollViewMinimumOffset) + totalHeight;
offsetHeight = MAX(offsetHeight, 0.0f);
offsetHeight = MIN(offsetHeight, totalHeight);
if (offsetHeight > barHeight * 1.0f) {
[self.backBarButtonItem setTintColor:[UIColor redColor]];
} else {
[self.backBarButtonItem setTintColor:[UIColor yellowColor]];
}
Let me provide the following example that can help you figure out or gain some ideas to better address the issue.
So in the storyboard (can be done programmatically), I have the following scenario:
That backBarButtonItem is actually 1stVC button in the NavigationBar.
In order to change the color of backBarButtonItem, you may implement the following code (or take a look):
import UIKit
class ViewController2: UIViewController {
var counter = 0 //any conditions you want to play with
override func viewDidLoad() {
super.viewDidLoad()
var color: UIColor = UIColor.purple //or yellow, by default
if(counter == 0){
color = UIColor.red
}
self.navigationController?.navigationBar.tintColor = color
}
}
It is done in the viewDidLoad() method of ViewController2 so that it can get configured as soon as this ViewController is opened.
Here, I just used counter variable as a simple example to create some condition based on which the color of backBarButtonItem should be changed. In your case, you have another condition.
So this is the output:

How to change top margin for UIBarButtonItem

I have a toolbar in my view. it contains a Bar button item with an icon, actually it's not an icon (it's a custom font). I'm using it to unify the icons with other web application for the same customer.
Anyway, how could I increase the top margin a little bit ... maybe the given example is showing a filter icon that I can easily replace it with real image icon (not a font). But some other icons, it's impossible.
Edit 1:
I'm using C# (Xamarin). Even if there is an object-c code. I'm ok with it.
var att = new UITextAttributes ();
att.Font = FontHelper.GetIconFont (32.0f);
this.btnFilter.SetTitleTextAttributes (att, UIControlState.Normal);
the custom icon font method:
public static UIFont GetIconFont(float size)
{
var nfloatSize = nfloat.Parse (((float)size).ToString ());
return UIFont.FromName(_fontIcons, nfloatSize);
}
Try something like this,
float offset = 3.0f;
UIBarButtonItem * barItem = [[UIBarButtonItem alloc] initWithTitle:#"title"
style:UIBarButtonItemStyleDone
target:nil action:#selector(someMessage)];
[barItem setBackgroundVerticalPositionAdjustment:offset forBarMetrics:UIBarMetricsDefault];
I'm thinking of the way of doing subclass UINavigationBar and override layoutSubviews and reposition the UIBarButtonItem. Also, don't forget to set UINavigationBar class to UINavigationBar subclass in Interface Builder. So you may try something like this:
// UINavigationBar subclass
#define NAVIGATION_MARGIN 3
#implementation NewNavigationBar
- (void)layoutSubviews {
[super layoutSubviews];
UINavigationItem *navigationItem = [self topItem];
subview = [[navigationItem leftBarButtonItem] customView];
if (subview) {
CGRect subviewFrame = subview.frame;
subview.frame.origin.x = NAVIGATION_BTN_MARGIN;
subview.frame.origin.y = (self.frame.size.height - subview.frame.size.height) / 2;
[subview setFrame:subviewFrame];
}
}
#end
I'm not sure what language your working in but use the backgroundVerticalPositionAdjustment property of UIBarButtonItem.
button.setBackgroundVerticalPositionAdjustment(3.0, forBarMetrics: .Default)
When using text / Ionicons, the following is what's needed to reposition the icon ( Xamarin code below ):
button.SetTitlePositionAdjustment(
new UIOffset() { Vertical = 15.0f, Horizontal = 5.0f },
UIBarMetrics.Default );

Turning off dimming by UIPopoverController

In iOS7, a popover causes the rest of the screen to be dimmed. As per the Apple docs:
The popover content is layered on top of your existing content and the background is dimmed automatically.
This is nice in most cases, but I have an app where the screen rearranges itself when the popover opens and stays responsive, so the dimming only causes confusion. Anyone knows if dimming can be disabled?
Doesn’t look like there’s anything in the API to support that—you can set the passthroughViews property to allow other views to be interacted with while the popover’s open, but that doesn’t affect the dimming. You may have to roll your own popover implementation or find a third-party version.
I can suggest you a custom control which is really nice work by its author. It do not dim the background. Further it has many customization.
Here is the github link for WYPopoverController
For me at works like this. I just work through all subviews if key window view, find _UIMirrorNinePatchView. _UIMirrorNinePatchView is apple class for that has four image views, these image views create the dimming background for 4 directions of PopOverPresentationController. More specifically you can look at this if you use view hierarchy debugger. So I walk through the array of these UIImageView and set UIImage to nil. This code paste in viewWillAppear of your destination controller(popOverContoller).
NSArray<UIView *> *arrayOfSubviews = [UIApplication sharedApplication].keyWindow.subviews.lastObject.subviews;
for (int i = 0; i < arrayOfSubviews.count; i++) {
if ([NSStringFromClass(arrayOfSubviews[i].class) isEqualToString:#"_UIMirrorNinePatchView"]) {
arrayOfSubviews[i].backgroundColor = [UIColor clearColor];
NSArray<UIImageView *> *arrayOfImageViews = arrayOfSubviews[i].subviews;
for (int j = 0; j < arrayOfImageViews.count; j++) {
arrayOfImageViews[j].image = nil;
}
}
}
In whole my UIPopOverController looks like this
And in view debugger, it looks so
So as you can understand, setting UIImage to nil will remove this dimming view.
This is the swift version to remove the dimming of UIPopoverController
let allSubViews: [UIView] = (UIApplication.shared.keyWindow?.subviews.last?.subviews)!
for index in 0...allSubViews.count - 1 {
allSubViews[index].removeFromSuperview()
if NSStringFromClass(allSubViews[index].classForCoder) == "_UIMirrorNinePatchView"
{
allSubViews[index].backgroundColor = UIColor.clear
let arrayofImages = allSubViews[index].subviews as! [UIImageView]
for imageIndex in 0...arrayofImages.count - 1 {
arrayofImages[imageIndex].image = nil
}
}
}
You can prevent the dimming by setting the UIPopoverBackgroundView for your popover and setting the background to be transparent for the background view.
You will need to re-implement how the popover draws the arrows, but you can find plenty of examples for that online.
Updated to work in iOS 13 with Swift 4
guard let transitionSubviews = UIApplication.shared.keyWindow?.subviews.last?.subviews else { return }
func findViews<T>(inView view: UIView, subclassOf targetType: T.Type) -> [T] {
return recursiveSubviews(inView: view).compactMap { $0 as? T }
}
func recursiveSubviews(inView view: UIView) -> [UIView] {
return view.subviews + view.subviews.flatMap { recursiveSubviews(inView: $0) }
}
for view in transitionSubviews {
view.backgroundColor = UIColor.clear
for imageView in findViews(inView: view, subclassOf: UIImageView.self) {
imageView.image = nil
}
}
If you choose to implement your custom UIPopoverBackgroundView, you can set the layer background to be clear - layer.shadowColor = UIColor.clearColor().CGColor.
However this will eliminate the dim and the shadow completely so you will have to put a border around the controller
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
This solved my problem with navigation bar dimming effect while transiting.

Change Color of MPVolumeView Route Button iOS 7

I am designing a Music app for iOS 7 and I want to put the "AirPlay" route selector button directly in my app. I am able to get the button placed just fine, however it doesn't show up because the icon is white and my background is white.
Is there a way to change the color of the Route Button?
Here is the code I'm using to create the button.
self.airPlayButton = [[MPVolumeView alloc] initWithFrame:CGRectZero];
self.airPlayButton.showsVolumeSlider = NO;
[self.airPlayButton sizeToFit];
self.airPlayButton.backgroundColor = [UIColor myGreenColor];
[self addSubview:self.airPlayButton];
Basically the picture below is what I want, except I want the icon green instead of just it's background.
in reviewing Adams answer I like more clarity in that task. So I safe-guarded that code a bit:
Objective-C:
for( UIView *wnd in volumeView.subviews ) {
if( [wnd isKindOfClass:[UIButton class] ]) {
UIButton *button = (UIButton*) wnd;
UIImage *img = button.currentImage;
UIImage *img2 = [img imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[volumeView setRouteButtonImage: img2 forState:UIControlStateNormal];
break;
}
}
Swift:
for view in volumeView.subviews {
if view.isKindOfClass(UIButton) {
let buttonOnVolumeView : UIButton = view as! UIButton
volumeView.setRouteButtonImage(buttonOnVolumeView.currentImage?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
break;
}
}
Now it reacts on the tintColor property of volumeView and if Apple decides to add another button or change the sequence this code will still work.
To expand on lanbo's answer, you can also get the original image of the Route Button and create a copy that uses the UIImageRenderingMode.AlwaysTemplate rendering mode. That way it heeds the current tintColor.
In Swift:
let volumeView = MPVolumeView()
if let routeButton = volumeView.subviews.last as? UIButton,
let routeButtonTemplateImage = routeButton.currentImage?.imageWithRenderingMode(.AlwaysTemplate)
{
volumeView.setRouteButtonImage(routeButtonTemplateImage, forState: .Normal)
}
Create your own image and Try
setRouteButtonImage:forState:
Assigns a button image to the specified control states.
- (void)setRouteButtonImage:(UIImage *)image forState:(UIControlState)state
Parameters
image - The image to associate with the specified states.
state - The control state with which to associate the image.
Discussion
Use this to customize the appearance of the route button when it is enabled, disabled, highlighted, and so on.
Available in iOS 6.0 and later.

How to access multiple buttons in access function?

I have two buttons in each row of a tableview. One is labeled "have it" the other "want it" Each button starts off at 20% opacity when the app starts. When one button is tapped the opacity is set to 100% . I need logic so that if one button is set to 100% opacity and the other one set at 20% is tapped, the first button needs to be set to 20% and the second button to 100% (so the opacity needs to be reversed).
Each button has it's own action that is run when pressed. I can access the button that is pressed and set the opacity with (UIButton *senderButton = (UIButton *)sender). However I need to set the opacity of the other button as well. How can access the other button (the one that was not pressed) inside of my action/function that is called when one is pressed? Thanks!
You can create an outlet for each button. So that you can set its property from any where within its container class.
if I correct understand your question, you can declare your buttons in header-file like this:
#interface myController : UIViewController
{
UIButton *b1;
UIButton *b2;
}
tmen in m-file (in viewDidLoad) you can set this buttons with one selector and different tags: (for more information about creation buttons: How do I create a basic UIButton programmatically?)
-(void)viewDidLoad
{
[super viewDidLoad];
b1 = [UIButton buttonwithType:UIButtonTypeCustom];
[b1 addTarget:self withAction:#selector(clickINMyButtons:) forState:UIControlTouchUPInside]; // sorry, I don't remember correct syntax, i'll correct this some later if you needed in it.
b1.tag = 1;
b1.frame = CGRectMake(0,0,12,12); //example
[self.view addSubView:b1];
}
alike declare b2 with different:
b2.tag = 2;
So, then you implement your selector with changing opacity:
-(void)clickINMyButtons:(UIButton *)sender
{
if (sender.tag == 1)
{
sender.alpha = 1; // or b1.alpha = 1;
b2.alpha = 0.2;
}
else if (sender.tag == 2)
{
sender.alpha = 1; // or b2.alpha = 1;
b1.alpha = 0.2;
}
}

Resources