Cómo ajustar el relleno izquierdo y derecho de UIToolBar

Creo una UIToolbar con código y otra con el constructor de interfaces. Pero, descubrí que las dos barras de herramientas tienen un relleno diferente a la izquierda y a la derecha que se muestra a continuación:

Desde Interface Builder:

enter image description here

Desde Código:

enter image description here

UIImage *buttonImage = [[UIImage imageNamed:@"button.png"] stretchableImageWithLeftCapWidth:10 topCapHeight:0];
UIButton *btnTest = [UIButton buttonWithType:UIButtonTypeCustom];
[btnTest setBackgroundImage:buttonImage forState:UIControlStateNormal];
[btnTest setTitle:@"Back" forState:UIControlStateNormal];   
[btnTest.titleLabel setFont:[UIFont boldSystemFontOfSize:13]];  
[btnTest setBackgroundImage:[imgToolbarButton stretchableImageWithLeftCapWidth:5 topCapHeight:0]  forState:UIControlStateNormal];
[btnTest addTarget:self action:@selector(clearDateEdit:) forControlEvents:UIControlEventTouchUpInside];
btnTest.frame = CGRectMake(0.0, 0.0, 50, 30);
UIBarButtonItem *btnTestItem = [[UIBarButtonItem alloc] initWithCustomView:btnTest];
[self.toolbar setItems:[NSArray arrayWithObjects:btnTestItem,nil]];
[btnTestItem release];

Mi pregunta es ¿cómo puedo ajustar el relleno izquierdo y derecho de UIToolbar por código?

Noticias

Descubrí que este problema de alineación solo le ocurre a UIBarButtonItem con customView de UIButton, la alineación está bien con UIBarButtonItem. Alguna idea de qué causa esto o qué resuelve esto.

La única solución en la que puedo pensar en este momento es configurar manualmente el marco.

preguntado el 16 de mayo de 11 a las 17:05

¿Por relleno te refieres al espacio entre botones? -

@TonyMocha, ¿estás seguro de que se ve diferente si solo agregas los dos botones por código con un espacio flexible entre ellos? -

@peterpillar. Relleno izquierdo y derecho para el primer y último botón. -

@nick weaver. Si. No estoy seguro si soy solo yo. Pero de alguna manera, tanto parece esta barra de herramientas 2, que tiene un espacio de relleno diferente. Me estoy rascando la cabeza. -

@TonyMocha Echa un vistazo nib2objc y deje que transforme su archivo nib en código objc legible. Puede que se cambie alguna propiedad que no es obvia. -

11 Respuestas

Tuve el mismo problema, y ​​hay un buen truco que puede hacer con un UIBarButtonSystemItemFixedSpace, agregue uno de estos con un ancho negativo antes de su primer botón y después de su último botón y moverá el botón al borde.

Por ejemplo, para deshacerse del margen derecho, agregue el siguiente elemento de la barra FixedSpace como último elemento:

Actualización para iOS 11 hasta 13

  • rápido

    let negativeSeperator = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
    negativeSeperator.width = 12
    

    El ancho debe ser positivo en la versión Swift


  • Objc

    UIBarButtonItem *negativeSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    negativeSeparator.width = -12;
    

Los márgenes de la izquierda y la derecha son 12px.

Actualización para iOS7: ¡los márgenes son 16px en iPhone y 20px en iPad!

respondido 11 mar '20, 11:03

Tuve que usar un ancho de -5, pero esto funcionó bien. Para el botón de la barra derecha, tuve que agregar un Separador negativo antes Y después para que funcione. - Kevin Elliott

Sí, funciona, pero parece una solución alternativa ... ¿no hay otra solución? - Bogdan

Kevin Elliott y cualquier otra persona interesada, no necesita un separador antes y después, justo antes. El código que estoy usando es [[self.navigationBar topItem] setRightBarButtonItems:@[negativeSeparator, customButton]]; con un Separador de negativos de -5 pt de ancho. Cuando se agrega de esta manera, el primero en la matriz es el más a la derecha, el segundo se coloca a su izquierda. Piense en los elementos del botón de la barra derecha como una pila y este orden "inverso" tiene sentido. - JK Laiho

@ craig-mellon Para iPhone 6, el 16px no funciona. Funciona con 20px. ¿Cuál es la mejor solución para resolver esto, en todos los dispositivos iPhone? - Vignesh PT

Utilización de self.navigationController.view.layoutMargins.right para el margen real. IOS8 + - BugaBuga

Antes de iOS 11

Tenías tres posibles soluciones:

  • El primero fue usar un elemento de espacio fijo UIBarButtonItem (barButtonSystemItem: .fixedSpace…) con un ancho negativo
  • La otra era anular la alineaciónRectInsets en la vista personalizada
  • El último, si usa UIButton como la vista personalizada de su elemento, puede anular contentEdgeInsets y hitTest (_: with :)

Absolutamente, la primera solución parece mejor que otras

UIBarButtonItem *negativeSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSeparator.width = -12;

si usa swift, el código se verá así

var negativeSeparator = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeSeparator.width = -12

Después de iOS 11 (contiene iOS 11)

desafortunadamente, toda la solución no se puede usar en iOS 11

  • primera solución: establecer un ancho negativo ya no funciona
  • segunda solución: anular la alineaciónRectInsets hará que parte del elemento ya no reciba toques
  • la última solución: creo que anular hitTest (_: with :) no es una buena idea, ¡no hagas esto, por favor!

También puede ver alguna sugerencia en el Foros de desarrolladores, pero después de mirar todos los comentarios, encontrará una sugerencia sobre este tema: crear una subclase UINavigationBar personalizada y hacer algo complejo.

¡Dios mío, todo lo que quiero hacer es cambiar el relleno, no la barra de navegación!

¡Afortunadamente, podemos hacer esto con un truco en iOS 11!

Aquí vamos:

1. crear un botón personalizado

  • establecer translatesAutoresizingMaskIntoConstraints como falso
  • anular alineación
    • elemento en el lado derecho use UIEdgeInsets (arriba: 0, izquierda: -8, abajo: 0, derecha: 8)
    • elemento en el lado izquierdo use UIEdgeInsets (arriba: 0, izquierda: 8, abajo: 0, derecha: -8)

      si no conoce la alineaciónRectInsets, puede leer este blog primeras

versión rápida

var customButton = UIButton(type: .custom)
customButton.overrideAlignmentRectInsets = UIEdgeInsets(top: 0, left: x, bottom: 0, right: -x) // you should do this in your own custom class
customButton.translatesAutoresizingMaskIntoConstraints = false;

versión objetivo-c

UIButton *customButton = [UIButton buttonWithType:UIButtonTypeCustom];
customButton.overrideAlignmentRectInsets = UIEdgeInsetsMake(0, x, 0, -x); // you should do this in your own custom class
customButton.translatesAutoresizingMaskIntoConstraints = NO;

2. crea un artículo con tu botón personalizado

versión rápida

var item = UIBarButtonItem(customView: customButton)

versión objetivo-c

UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:customButton]

3. cree un UIBarButtonItem de tipo FixedSpace con positivo anchura

establecer un positivo valor, no un valor negativo

versión rápida

var positiveSeparator = UIBarButtonItem(barButtonSystemItem:.fixedSpace, target: nil, action: nil)
positiveSeparator.width = 8

versión objetivo-c

UIBarButtonItem *positiveSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
positiveSeparator.width = 8;

4. establecer una matriz en leftitems o rightitems

El elemento FixedSpace type UIBarButton debe ser el primer elemento en matriz.

versión rápida

self.navigationItem.leftBarButtonItems = [positiveSeparator, item, ...]

versión objetivo-c

self.navigationItem.leftBarButtonItems = @{positiveSeparator, item, ...}

Bien hecho

después de realizar todos los pasos, verá que el relleno se vuelve más pequeño y el área de tipografía parece correcta.

Si encuentra algo mal, por favor avíseme. ¡Haré todo lo posible para responder a su pregunta!

Nota:

Antes de iOS 11, debería preocuparse por el ancho de la pantalla del dispositivo; si la pantalla es de 5.5 pulgadas, el negativo es -12 pt, en otras pantallas es -8pt.

Si usa mi solución en iOS 11, no necesita preocuparse por la pantalla del dispositivo, solo configure 8pt. Debería preocuparse por la posición del elemento en la barra de navegación, lado izquierdo o derecho, esto afectará la alineación de su vista personalizada.

¿Quieres más área para tocar?

Si desea prometer que el área de su grifo es mayor que 44 * 44, puede anular el método a continuación

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    CGSize acturalSize = self.frame.size;
    CGSize minimumSize = kBarButtonMinimumTapAreaSize;
    CGFloat verticalMargin = acturalSize.height - minimumSize.height >= 0 ? 0 : ((minimumSize.height - acturalSize.height ) / 2);
    CGFloat horizontalMargin = acturalSize.width - minimumSize.width >= 0 ? 0 : ((minimumSize.width - acturalSize.width ) / 2);
    CGRect newArea = CGRectMake(self.bounds.origin.x - horizontalMargin, self.bounds.origin.y - verticalMargin, self.bounds.size.width + 2 * horizontalMargin, self.bounds.size.height + 2 * verticalMargin);
    return CGRectContainsPoint(newArea, point);
}

Respondido el 15 de diciembre de 17 a las 09:12

Esto funciona perfectamente para mí, excepto que el área objetivo es muy pequeña, lo que la hace casi inutilizable. - Eric

@Eric si desea modificar el área de toque, puede anular los métodos del botón como `- (BOOL) pointInside: (CGPoint) point withEvent: (UIEvent *) event` - SketchK

overrideAlignmentRectInsets no es un atributo y alignmentRectInsets solo está disponible para anular - vea mi respuesta - RichAppz

@Eric, si lees mi respuesta detenidamente, encontrarás que te dije que sobrescribiera alineaciónRectInset al comienzo del capítulo de ios 11 - SketchK

Usé customButton.contentEdgeInsets = UIEdgeInsets (arriba: 0, izquierda: -25, abajo: 0, derecha: 0) en lugar de overrideAlignmentRectInsets. Y con esto, no tuve que usar PositiveSeparator. - Sujal

Esta es una gran pregunta e incluso con las soluciones proporcionadas, no resolvió el problema en iOS11.

SOLUCIÓN DE TRABAJO

Si crea una nueva extensión de UIButton:

class BarButton: UIButton {

    override var alignmentRectInsets: UIEdgeInsets {
        return UIEdgeInsetsMake(0, 16, 0, 16)
    }

}

Luego, cuando lo use en su diseño:

lazy var btnLogin: BarButton = {
    let btn = BarButton(type: .custom)
    // Add your button settings

    btn.widthAnchor.constraint(equalToConstant: self.frame.size.width).isActive = true
    btn.heightAnchor.constraint(equalToConstant: 50).isActive = true
    btn.translatesAutoresizingMaskIntoConstraints = false
    return btn
}()

Esto resolvió un problema muy molesto, si tiene algún problema, avíseme.

Respondido el 24 de enero de 18 a las 19:01

Este es un enfoque mucho más limpio y debería ser la solución aceptada: evanjmg

problema resuelto pero con un efecto secundario, si el fondo del botón personalizado es diferente con la barra de navegación en el controlador siguiente / anterior, entonces se presentará un botón recortado al presionar / hacer estallar. - wang

Esta es una buena solución, pero para mí el área táctil estaba apagada. Tuve que agregar el "PositiveSeparator" mencionado anteriormente para cambiar el área táctil. - Joe

usa este botón

UIBarButtonItem *spaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                                                                           target:nil
                                                                           action:nil];

contestado el 17 de mayo de 11 a las 14:05

Me encontré con el mismo problema. Finalmente, resolví esto subclasificando UIToolbar y anulando el método de subvistas de diseño.

- (void)layoutSubviews {

  [super layoutSubviews];

  if (leftItem_ && leftItem_.customView
      && [leftItem_.customView isKindOfClass:[UIButton class]]) {
    CGRect newFrame = leftItem_.customView.frame;
    newFrame.origin.x = 0;   // reset the original point x to 0; default is 12, wired number
    leftItem_.customView.frame = newFrame;    
  }

  if (rightItem_ && rightItem_.customView
      && [rightItem_.customView isKindOfClass:[UIButton class]]) {
    CGRect newFrame = rightItem_.customView.frame;
    newFrame.origin.x = self.frame.size.width - CGRectGetWidth(newFrame);
    rightItem_.customView.frame = newFrame;
  }

}

Respondido 25 ago 11, 09:08

¿Cómo configuró esa barra de herramientas personalizada en el controlador de navegación actual? - Prcela

Puede cambiar el desplazamiento y el ancho de su barra de herramientas, si desea utilizar vista personalizada (initWithCustomView)

[myToolBar setFrame:CGRectMake(-10, 0, [UIScreen mainScreen].bounds.size.width+10, 44)];

Respondido el 07 de enero de 13 a las 13:01

Creo que es una solución mucho más limpia agregar el UIBarButtonSystemItemFixedSpace como se mencionó anteriormente; de ​​esa manera, no se arriesga a que la barra de herramientas se superponga a otros controles en la interfaz de usuario, causando otros problemas. Especialmente si la barra de herramientas es transparente. - Oler

Me encontré con esto al intentar centrar una barra de herramientas como una subvista de un UIButton. Como la barra de herramientas debía tener un ancho reducido, la mejor solución era un botón flexible en cada lado:

UIBarButtonItem flexibleButtonItemLeft = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];

UIBarButtonItem centeredButtonItem = ...

UIBarButtonItem flexibleButtonItemRight = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];

UIToolbar toolbar = ...

toolbar.items = @[flexibleButtonItemLeft, centeredButtonItem, flexibleButtonItemRight];

Respondido 08 Jul 15, 09:07

Finalmente, para tener una imagen de fondo personalizada para UIBarButtonItem y para acomodar la alineación, abandoné UIBarButtonItem y agregué UIButton manualmente.

UIImage *buttonImage = [[UIImage imageNamed:@"button.png"] stretchableImageWithLeftCapWidth:10 topCapHeight:0];
UIButton *btnTest = [UIButton buttonWithType:UIButtonTypeCustom];
[btnTest setBackgroundImage:buttonImage forState:UIControlStateNormal];
[btnTest setTitle:@"Back" forState:UIControlStateNormal];   
[btnTest.titleLabel setFont:[UIFont boldSystemFontOfSize:13]];  
[btnTest setBackgroundImage:[imgToolbarButton stretchableImageWithLeftCapWidth:5 topCapHeight:0]  forState:UIControlStateNormal];
[btnTest addTarget:self action:@selector(clearDateEdit:) forControlEvents:UIControlEventTouchUpInside];
btnTest.frame = CGRectMake(0.0, 0.0, 50, 30);
[self.toolbar addSubview:btnTest];
[btnTestItem release];

contestado el 19 de mayo de 11 a las 08:05

Creo que una forma complicada es usar Aspects (o método swizzling), como explotó:

[UINavigationBar aspect_hookSelector:@selector(layoutSubviews) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info){
        UINavigationBar *bar = info.instance;

        //[bar layoutSubviews];

        if ([bar isKindOfClass:[UINavigationBar class]]) {
            if (@available(iOS 11, *)) {
                bar.layoutMargins = UIEdgeInsetsZero;

                for (UIView *subview in bar.subviews) {
                    if ([NSStringFromClass([subview class]) containsString:@"ContentView"]) {
                        UIEdgeInsets oEdges = subview.layoutMargins;
                        subview.layoutMargins = UIEdgeInsetsMake(0, 0, 0, oEdges.right);
                    }
                }
            }
        }
    } error:nil];

Respondido el 09 de diciembre de 17 a las 11:12

Sólo agregar constantes negativas para usted restricciones iniciales y finales de los UIToolbar. De esa manera, la barra de herramientas comienza y termina fuera de su vista y, por lo tanto, tiene menos relleno.

Respondido 06 ago 19, 12:08

En mi caso, necesito disminuir el relleno izquierdo de leftBarButtonItem y mantener los toques en toda el área.

class FilterButton: Button {

override init() {
    super.init()
    setImage(UIImage(named: "filter"), for: .normal)
    backgroundColor = UIColor.App.ultraLightGray
    layer.cornerRadius = 10
    
    snp.makeConstraints {
        $0.size.equalTo(36)
    }
}

override var alignmentRectInsets: UIEdgeInsets {
    return UIEdgeInsets(top: 0, left: 4, bottom: 0, right: -4)
}
}


let filterButton = FilterButton()
filterButton.addTarget(self, action: #selector(filterTapped(_:)), for: .touchUpInside)
let filterBarButton = UIBarButtonItem(customView: filterButton)
let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
    
navigationItem.setLeftBarButtonItems([spacer, filterBarButton], animated: false)

respondido 23 mar '21, 18:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.