GrabDuck

iOS Инструменты разработчика

:

Вступление


Всем привет, меня зовут Григорий, последние 5 лет занимался программированием под iOS. Сейчас решил сменить сферу деятельности и ударился в веб, но чтобы добро не пропадало, хочу поделиться с сообществом своими наработками, накопившимися за это время. Библиотеки выложены на GitHub и добавлены в CocoaPods. Инструкции по установке и использованию вы сможете найти по ссылкам на GitHub, здесь же будет краткое описание.

Минимальная поддерживаемая версия — iOS 6.0.



UIAlertView — один из часто используемых компонентов при разработке на iOS. Выглядит и работает из коробки замечательно, проблема в том, что возможностей кастомизировать его Apple практически не дает, есть только несколько стандартно заданных стилей отображения. А что если нам нужно вставить внутрь свою вьюху, или банально поменять цвет кнопок или фона? Вот и было принято решение написать универсальный класс, который повторял бы работу UIAlertView, но имел широкие возможности по настройке. Так и появился LGAlertView.

При инициализации у нас есть возможность выбора между несколькими стилями:

Немного кода
Стандартный (заголовок + сообщение + кнопки):
- (instancetype)initWithTitle:(NSString *)title
message:(NSString *)message
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;

Cо встроенной вьюхой (заголовок + сообщение + UIView + кнопки):
- (instancetype)initWithViewStyleWithTitle:(NSString *)title
message:(NSString *)message
view:(UIView *)view
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;

C индикатором активности (заголовок + сообщение + UIActivityIndicatorView + кнопки):
- (instancetype)initWithActivityIndicatorStyleWithTitle:(NSString *)title
message:(NSString *)message
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;

C полосой прогресса (заголовок + сообщение + UIProgressView + кнопки):
- (instancetype)initWithActivityIndicatorStyleWithTitle:(NSString *)title
message:(NSString *)message
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;

C полями ввода (заголовок + сообщение + поля ввода + кнопки):
- (instancetype)initWithActivityIndicatorStyleWithTitle:(NSString *)title
message:(NSString *)message
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;


Для возможности отлавливать события есть несколько путей: делегирование, как в стандартном UIAlertView, и блоки (кому как больше нравится). Кроме того, предусмотрены NSNotification для появления и исчезновения LGAlertView с экрана. Сначала думал добавить нотификации и для кнопок, но решил, что все-таки это будет лишним.
Немного кода
Делегирование:
@property (assign, nonatomic) id<LGAlertViewDelegate> delegate;

- (void)alertViewWillShow:(LGAlertView *)alertView;
- (void)alertViewWillDismiss:(LGAlertView *)alertView;
- (void)alertViewDidShow:(LGAlertView *)alertView;
- (void)alertViewDidDismiss:(LGAlertView *)alertView;
- (void)alertView:(LGAlertView *)alertView buttonPressedWithTitle:(NSString *)title index:(NSUInteger)index;
- (void)alertViewCancelled:(LGAlertView *)alertView;
- (void)alertViewDestructiveButtonPressed:(LGAlertView *)alertView;

Блоки:
@property (strong, nonatomic) void (^willShowHandler)(LGAlertView *alertView);
@property (strong, nonatomic) void (^willDismissHandler)(LGAlertView *alertView);
@property (strong, nonatomic) void (^didShowHandler)(LGAlertView *alertView);
@property (strong, nonatomic) void (^didDismissHandler)(LGAlertView *alertView);
@property (strong, nonatomic) void (^actionHandler)(LGAlertView *alertView, NSString *title, NSUInteger index);
@property (strong, nonatomic) void (^cancelHandler)(LGAlertView *alertView, BOOL onButton);
@property (strong, nonatomic) void (^destructiveHandler)(LGAlertView *alertView);

NSNotifications:
kLGAlertViewWillShowNotification;
kLGAlertViewWillDismissNotification;
kLGAlertViewDidShowNotification;
kLGAlertViewDidDismissNotification;


Для настройки внешнего вида есть много свойств. Перечислять их долго, думаю из названий должно быть все понятно:
Немного кода
/** Default is YES */
@property (assign, nonatomic, getter=isCancelOnTouch) BOOL cancelOnTouch;
/** Set highlighted buttons background color to blue, and set highlighted destructive button background color to red. Default is YES */
@property (assign, nonatomic, getter=isColorful) BOOL colorful;

@property (strong, nonatomic) UIColor *tintColor;
@property (strong, nonatomic) UIColor *coverColor;
@property (strong, nonatomic) UIColor *backgroundColor;
@property (assign, nonatomic) CGFloat layerCornerRadius;
@property (strong, nonatomic) UIColor *layerBorderColor;
@property (assign, nonatomic) CGFloat layerBorderWidth;
@property (strong, nonatomic) UIColor *layerShadowColor;
@property (assign, nonatomic) CGFloat layerShadowRadius;

@property (assign, nonatomic) CGFloat heightMax;
@property (assign, nonatomic) CGFloat widthMax;

@property (strong, nonatomic) UIColor         *titleTextColor;
@property (assign, nonatomic) NSTextAlignment titleTextAlignment;
@property (strong, nonatomic) UIFont          *titleFont;

@property (strong, nonatomic) UIColor         *messageTextColor;
@property (assign, nonatomic) NSTextAlignment messageTextAlignment;
@property (strong, nonatomic) UIFont          *messageFont;

@property (strong, nonatomic) UIColor         *buttonsTitleColor;
@property (strong, nonatomic) UIColor         *buttonsTitleColorHighlighted;
@property (assign, nonatomic) NSTextAlignment buttonsTextAlignment;
@property (strong, nonatomic) UIFont          *buttonsFont;
@property (strong, nonatomic) UIColor         *buttonsBackgroundColorHighlighted;
@property (assign, nonatomic) NSUInteger      buttonsNumberOfLines;
@property (assign, nonatomic) NSLineBreakMode buttonsLineBreakMode;
@property (assign, nonatomic) BOOL            buttonsAdjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         buttonsMinimumScaleFactor;

@property (strong, nonatomic) UIColor         *cancelButtonTitleColor;
@property (strong, nonatomic) UIColor         *cancelButtonTitleColorHighlighted;
@property (assign, nonatomic) NSTextAlignment cancelButtonTextAlignment;
@property (strong, nonatomic) UIFont          *cancelButtonFont;
@property (strong, nonatomic) UIColor         *cancelButtonBackgroundColorHighlighted;
@property (assign, nonatomic) NSUInteger      cancelButtonNumberOfLines;
@property (assign, nonatomic) NSLineBreakMode cancelButtonLineBreakMode;
@property (assign, nonatomic) BOOL            cancelButtonAdjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         cancelButtonMinimumScaleFactor;

@property (strong, nonatomic) UIColor         *destructiveButtonTitleColor;
@property (strong, nonatomic) UIColor         *destructiveButtonTitleColorHighlighted;
@property (assign, nonatomic) NSTextAlignment destructiveButtonTextAlignment;
@property (strong, nonatomic) UIFont          *destructiveButtonFont;
@property (strong, nonatomic) UIColor         *destructiveButtonBackgroundColorHighlighted;
@property (assign, nonatomic) NSUInteger      destructiveButtonNumberOfLines;
@property (assign, nonatomic) NSLineBreakMode destructiveButtonLineBreakMode;
@property (assign, nonatomic) BOOL            destructiveButtonAdjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         destructiveButtonMinimumScaleFactor;

@property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle;
@property (strong, nonatomic) UIColor                      *activityIndicatorViewColor;

@property (strong, nonatomic) UIColor *progressViewProgressTintColor;
@property (strong, nonatomic) UIColor *progressViewTrackTintColor;
@property (strong, nonatomic) UIImage *progressViewProgressImage;
@property (strong, nonatomic) UIImage *progressViewTrackImage;

@property (strong, nonatomic) UIColor         *progressLabelTextColor;
@property (assign, nonatomic) NSTextAlignment progressLabelTextAlignment;
@property (strong, nonatomic) UIFont          *progressLabelFont;

@property (strong, nonatomic) UIColor *separatorsColor;

@property (assign, nonatomic) UIScrollViewIndicatorStyle indicatorStyle;


Чтобы показывать или скрывать LGAlertView предусмотрены следующие методы:
- (void)showAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)dismissAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;

Кроме того удалось добиться правильного поведения в случае, когда появляется несколько вьюх подряд без закрытия предыдущих. Вы можете не боясь комбинировать UIAlertView, UIActionSheet, LGAlertView и LGActionSheet. При появлении новых старые будут исчезать, а при исчезновении — появляться.

Причины появления и принцип действия аналогичны LGAlertView.

Стили при инициализации:

Немного кода
Стандартный (сообщение + кнопки):
- (instancetype)initWithTitle:(NSString *)title
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;

Cо встроенной вьюхой (сообщение + UIView + кнопки):
- (instancetype)initWithTitle:(NSString *)title
view:(UIView *)view
buttonTitles:(NSArray *)buttonTitles
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle;


События отлавливаются с помощью делегирования, блоков и нотификаций:
Немного кода
Делегирование:
@property (assign, nonatomic) id<LGActionSheetDelegate> delegate;

- (void)actionSheetWillShow:(LGActionSheet *)actionSheet;
- (void)actionSheetWillDismiss:(LGActionSheet *)actionSheet;
- (void)actionSheetDidShow:(LGActionSheet *)actionSheet;
- (void)actionSheetDidDismiss:(LGActionSheet *)actionSheet;
- (void)actionSheet:(LGActionSheet *)actionSheet buttonPressedWithTitle:(NSString *)title index:(NSUInteger)index;
- (void)actionSheetCancelled:(LGActionSheet *)actionSheet;
- (void)actionSheetDestructiveButtonPressed:(LGActionSheet *)actionSheet;

Блоки:
@property (strong, nonatomic) void (^willShowHandler)(LGActionSheet *actionSheet);
@property (strong, nonatomic) void (^willDismissHandler)(LGActionSheet *actionSheet);
@property (strong, nonatomic) void (^didShowHandler)(LGActionSheet *actionSheet);
@property (strong, nonatomic) void (^didDismissHandler)(LGActionSheet *actionSheet);
@property (strong, nonatomic) void (^actionHandler)(LGActionSheet *actionSheet, NSString *title, NSUInteger index);
@property (strong, nonatomic) void (^cancelHandler)(LGActionSheet *actionSheet, BOOL onButton);
@property (strong, nonatomic) void (^destructiveHandler)(LGActionSheet *actionSheet);

NSNotifications:
kLGActionSheetWillShowNotification;
kLGActionSheetWillDismissNotification;
kLGActionSheetDidShowNotification;
kLGActionSheetDidDismissNotification;


Настройка внешнего вида и анимаций (по умолчанию анимации для iPhone и iPad различается):
Немного кода
@property (assign, nonatomic) LGActionSheetTransitionStyle transitionStyle;

/** Default is YES */
@property (assign, nonatomic, getter=isCancelOnTouch) BOOL cancelOnTouch;
/** Set highlighted buttons background color to blue, and set highlighted destructive button background color to red. Default is YES */
@property (assign, nonatomic, getter=isColorful) BOOL colorful;

@property (strong, nonatomic) UIColor *tintColor;
@property (strong, nonatomic) UIColor *coverColor;
@property (strong, nonatomic) UIColor *backgroundColor;
@property (assign, nonatomic) CGFloat layerCornerRadius;
@property (strong, nonatomic) UIColor *layerBorderColor;
@property (assign, nonatomic) CGFloat layerBorderWidth;
@property (strong, nonatomic) UIColor *layerShadowColor;
@property (assign, nonatomic) CGFloat layerShadowRadius;

@property (assign, nonatomic) CGFloat heightMax;
@property (assign, nonatomic) CGFloat widthMax;

@property (strong, nonatomic) UIColor         *titleTextColor;
@property (assign, nonatomic) NSTextAlignment titleTextAlignment;
@property (strong, nonatomic) UIFont          *titleFont;

@property (strong, nonatomic) UIColor         *buttonsTitleColor;
@property (strong, nonatomic) UIColor         *buttonsTitleColorHighlighted;
@property (assign, nonatomic) NSTextAlignment buttonsTextAlignment;
@property (strong, nonatomic) UIFont          *buttonsFont;
@property (strong, nonatomic) UIColor         *buttonsBackgroundColorHighlighted;
@property (assign, nonatomic) NSUInteger      buttonsNumberOfLines;
@property (assign, nonatomic) NSLineBreakMode buttonsLineBreakMode;
@property (assign, nonatomic) BOOL            buttonsAdjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         buttonsMinimumScaleFactor;

@property (strong, nonatomic) UIColor         *cancelButtonTitleColor;
@property (strong, nonatomic) UIColor         *cancelButtonTitleColorHighlighted;
@property (assign, nonatomic) NSTextAlignment cancelButtonTextAlignment;
@property (strong, nonatomic) UIFont          *cancelButtonFont;
@property (strong, nonatomic) UIColor         *cancelButtonBackgroundColorHighlighted;
@property (assign, nonatomic) NSUInteger      cancelButtonNumberOfLines;
@property (assign, nonatomic) NSLineBreakMode cancelButtonLineBreakMode;
@property (assign, nonatomic) BOOL            cancelButtonAdjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         cancelButtonMinimumScaleFactor;

@property (strong, nonatomic) UIColor         *destructiveButtonTitleColor;
@property (strong, nonatomic) UIColor         *destructiveButtonTitleColorHighlighted;
@property (assign, nonatomic) NSTextAlignment destructiveButtonTextAlignment;
@property (strong, nonatomic) UIFont          *destructiveButtonFont;
@property (strong, nonatomic) UIColor         *destructiveButtonBackgroundColorHighlighted;
@property (assign, nonatomic) NSUInteger      destructiveButtonNumberOfLines;
@property (assign, nonatomic) NSLineBreakMode destructiveButtonLineBreakMode;
@property (assign, nonatomic) BOOL            destructiveButtonAdjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         destructiveButtonMinimumScaleFactor;

@property (strong, nonatomic) UIColor *separatorsColor;

@property (assign, nonatomic) UIScrollViewIndicatorStyle indicatorStyle;


Чтобы показывать или скрывать LGActionSheet предусмотрены следующие методы:
- (void)showAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)dismissAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;


Без выезжающих боковых меню в наше время не обходится практически ни одно серьезное приложение. В какой-то момент надоело писать для каждого проекта разовые решения и была создана данная библиотека.

Краткий список возможностей:

  • Поддерживается как левое меню, так и правое
  • Различные виды анимаций
  • Показ и скрытие по нажатию кнопки и по жесту
  • Настройка правил показа для разных девайсов и ориентаций (например можно сделать меню нескрываемым для landscape ориентации на iPad)
  • На выбор скрывать или показывать статус бар
  • Широкие возможности по настройке внешнего вида

Добавить данный контроллер в проект довольно просто. Нужно указать ваш корневой контроллер (обычно это UINavigationController) как корневой контроллер LGSideMenuController'a… звучит немного тавтологично, приведу пример:

Обычная инициализация корневого контроллера в AppDelegate.m:

ViewController *viewController = [ViewController new];

UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];

window.rootViewController = navigationController;

Инициализация с LGSideMenuController'ом:
ViewController *viewController = [ViewController new];

UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];

LGSideMenuController *sideMenuController = [[LGSideMenuController alloc] initWithRootViewController:navigationController];

window.rootViewController = sideMenuController;

Настроить боковые меню тоже не сложно. Сначала необходимо включить те меню, которые вам нужны (левое, правое или обе):
[sideMenuController setLeftViewEnabledWithWidth:250.f // необходимая ширина области
presentationStyle:LGSideMenuPresentationStyleScaleFromBig // стиль анимации
alwaysVisibleOptions:0]; // правила показа

Далее добавьте свои вьюхи, которые будут показываться в боковом меню:
TableViewController *leftViewController = [TableViewController new];

[sideMenuController.leftView addSubview:leftViewController.tableView];

Чтобы показывать или скрывать боковые меню предусмотрены следующие методы:
- (void)showLeftViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)hideLeftViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showHideLeftViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;

- (void)showRightViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)hideRightViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showHideRightViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;

Также присутствуют следующие NSNotifications:
kLGSideMenuControllerWillShowLeftViewNotification;
kLGSideMenuControllerWillDismissLeftViewNotification;
kLGSideMenuControllerDidShowLeftViewNotification;
kLGSideMenuControllerDidDismissLeftViewNotification;

kLGSideMenuControllerWillShowRightViewNotification;
kLGSideMenuControllerWillDismissRightViewNotification;
kLGSideMenuControllerDidShowRightViewNotification;
kLGSideMenuControllerDidDismissRightViewNotification;



Google последнее время активно продвигает свой Material Design, одним из компонентов которого является кнопка "+", вызывающая какие-либо дополнительные опции. Решение довольно интересное. На моей практике был заказчик, который просил подобный функционал реализовать на iOS. Поэтому тянуть резину не стал и сразу решил написать универсальное решение.

Краткий список возможностей:

  • Можно добавить на любую вьюху
  • Если добавлено на UIScrollView то при скролле будет скрываться
  • Различные анимации появления кнопок
  • Можно выводить в любом из углов
  • Широкие возможности по настройке внешнего вида

Инициализация:
- (instancetype)initWithView:(UIView *)view
numberOfButtons:(NSUInteger)numberOfButtons
showsPlusButton:(BOOL)showsPlusButton;

События отлавливаются с помощью делегирования или блоков:
Немного кода
Делегирование:
@property (assign, nonatomic) id<LGPlusButtonsViewDelegate> delegate;

- (void)plusButtonsView:(LGPlusButtonsView *)plusButtonsView buttonPressedWithTitle:(NSString *)title description:(NSString *)description index:(NSUInteger)index;
- (void)plusButtonsViewPlusButtonPressed:(LGPlusButtonsView *)plusButtonsView;

Блоки:
@property (strong, nonatomic) void (^actionHandler)(LGPlusButtonsView *plusButtonView, NSString *title, NSString *description, NSUInteger index);
@property (strong, nonatomic) void (^plusButtonActionHandler)(LGPlusButtonsView *plusButtonView);


Настройка внешнего вида и анимаций:
Немного кода
@property (assign, nonatomic, getter=isShowWhenScrolling) BOOL showWhenScrolling;

@property (strong, nonatomic) LGPlusButton *plusButton;

/** First is plusButton */
@property (strong, nonatomic) NSMutableArray *buttons;
/** First is plusButton description */
@property (strong, nonatomic) NSMutableArray *descriptions;

@property (assign, nonatomic) UIEdgeInsets contentInset;
@property (assign, nonatomic) UIEdgeInsets buttonInset;
@property (assign, nonatomic) CGSize       buttonsSize;
@property (assign, nonatomic) CGSize       plusButtonSize;
/** Description horizontal offset from button, default is 6.f */
@property (assign, nonatomic) CGFloat      descriptionOffsetX;

@property (assign, nonatomic) LGPlusButtonsAppearingAnimationType appearingAnimationType;
@property (assign, nonatomic) LGPlusButtonsAppearingAnimationType buttonsAppearingAnimationType;
@property (assign, nonatomic) LGPlusButtonAnimationType           plusButtonAnimationType;
@property (assign, nonatomic) LGPlusButtonsViewPosition           position;

- (void)setButtonsTitles:(NSArray *)titles forState:(UIControlState)state;
- (void)setButtonsTitleColor:(UIColor *)titleColor forState:(UIControlState)state;
- (void)setButtonsImage:(UIImage *)image forState:(UIControlState)state;
- (void)setButtonsBackgroundImage:(UIImage *)backgroundImage forState:(UIControlState)state;
- (void)setButtonsBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state;
- (void)setButtonsTitleFont:(UIFont *)font;

- (void)setDescriptionsTexts:(NSArray *)texts;
- (void)setDescriptionsTextColor:(UIColor *)textColor;
- (void)setDescriptionsBackgroundColor:(UIColor *)backgroundColor;
- (void)setDescriptionsFont:(UIFont *)font;

- (void)setButtonsClipsToBounds:(BOOL)clipsToBounds;
- (void)setButtonsContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets;
- (void)setButtonsAdjustsImageWhenHighlighted:(BOOL)adjustsImageWhenHighlighted;

- (void)setButtonsLayerMasksToBounds:(BOOL)masksToBounds;
- (void)setButtonsLayerCornerRadius:(CGFloat)cornerRadius;
- (void)setButtonsLayerBorderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth;
- (void)setButtonsLayerShadowColor:(UIColor *)shadowColor shadowOpacity:(float)shadowOpacity shadowOffset:(CGSize)shadowOffset shadowRadius:(CGFloat)shadowRadius;


Чтобы показывать или скрывать LGPlusButtonsView, предусмотрены следующие методы:
// для всех кнопок, включая кнопку "+"
- (void)showAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)hideAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;

// только дополнительные кнопки
- (void)showButtonsAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)hideButtonsAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;


Довольно часто в новостных приложениях приходится делать фильтры, к примеру, для выбора категории выводимых материалов. Логика у них вполне похожа, обычно это выпадающая таблица с названиями. Данный функционал и реализует LGFilterView.

Краткий список возможностей:

  • Может показывать по умолчанию как таблицу, так и кастомную вьюху
  • Есть несколько стилей на выбор
  • Довольно широкие возможности по настройке внешнего вида

Инициализировать можно либо со списком возможных названий (тогда фильтр будет выглядеть как таблица), либо со своей кастомной вьюхой (которую фильтр будет показывать):
- (instancetype)initWithView:(UIView *)view;
- (instancetype)initWithTitles:(NSArray *)titles;

События отлавливаются с помощью делегирования, блоков и нотификаций:
Немного кода
Делегирование:
@property (assign, nonatomic) id<LGFilterViewDelegate> delegate;

- (void)filterViewWillShow:(LGFilterView *)filterView;
- (void)filterViewWillDismiss:(LGFilterView *)filterView;
- (void)filterViewDidShow:(LGFilterView *)filterView;
- (void)filterViewDidDismiss:(LGFilterView *)filterView;
- (void)filterView:(LGFilterView *)filterView buttonPressedWithTitle:(NSString *)title index:(NSUInteger)index;
- (void)filterViewCancelled:(LGFilterView *)filterView;

Блоки:
@property (strong, nonatomic) void (^willShowHandler)(LGFilterView *filterView);
@property (strong, nonatomic) void (^willDismissHandler)(LGFilterView *filterView);
@property (strong, nonatomic) void (^didShowHandler)(LGFilterView *filterView);
@property (strong, nonatomic) void (^didDismissHandler)(LGFilterView *filterView);
@property (strong, nonatomic) void (^actionHandler)(LGFilterView *filterView, NSString *title, NSUInteger index);
@property (strong, nonatomic) void (^cancelHandler)(LGFilterView *filterView);

NSNotifications:
kLGFilterViewWillShowNotification;
kLGFilterViewWillDismissNotification;
kLGFilterViewDidShowNotification;
kLGFilterViewDidDismissNotification;


Настройка внешнего вида и анимаций (по умолчанию анимации для iPhone и iPad различается):
Немного кода
@property (assign, nonatomic) LGFilterViewTransitionStyle transitionStyle;

@property (assign, nonatomic) CGPoint      offset;
@property (assign, nonatomic) UIEdgeInsets contentInset;
@property (assign, nonatomic) CGFloat      heightMax;

@property (assign, nonatomic, getter=isSeparatorsVisible) BOOL separatorsVisible;
@property (strong, nonatomic) UIColor      *separatorsColor;
@property (assign, nonatomic) UIEdgeInsets separatorsEdgeInsets;

@property (strong, nonatomic) UIColor *titleColor;
@property (strong, nonatomic) UIColor *titleColorHighlighted;
@property (strong, nonatomic) UIColor *titleColorSelected;

@property (strong, nonatomic) UIColor *backgroundColorHighlighted;
@property (strong, nonatomic) UIColor *backgroundColorSelected;

@property (strong, nonatomic) UIFont          *font;
@property (assign, nonatomic) NSUInteger      numberOfLines;
@property (assign, nonatomic) NSLineBreakMode lineBreakMode;
@property (assign, nonatomic) NSTextAlignment textAlignment;
@property (assign, nonatomic) BOOL            adjustsFontSizeToFitWidth;
@property (assign, nonatomic) CGFloat         minimumScaleFactor;

@property (assign, nonatomic) CGFloat cornerRadius;
@property (assign, nonatomic) CGFloat borderWidth;
@property (strong, nonatomic) UIColor *borderColor;

@property (assign, nonatomic) UIScrollViewIndicatorStyle indicatorStyle;


Чтобы показывать или скрывать фильтр предусмотрены следующие методы:
- (void)showInView:(UIView *)view animated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)dismissAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;


«Потяни, чтобы обновить» — очень модная фича, которая есть практически в каждом приложении. Даже Apple не удержалась и в iOS 6 добавила данный функционал, но почему-то только для UITableView, а UICollectionView и UIScrollView остались за бортом. Хотя при помощи некоторых костылей стандартный «pull to refresh» можно прикрутить и для UICollectionView, но костыли нам не нужны. По правде сказать различных «рефрешей» полно на гитхабе, сам долго искал подходящий, но в основном там либо заброшенные популярные старые версии, у которых накопился ворох различных проблем, или очень крутые библиотеки, которые делают просто невообразимые вещи, но слишком изощрены в дизайне, чтобы была возможность использовать их в любом проекте. Поэтому решил постараться сделать универсальный, кастомизируемый и нейтральный «pull to refresh».

При инициализации нужно указать родительскую вьюху, которая должна быть UIScrollView или наследуемым классом (UITableView или UICollectionView. По идее должно работать и с UIWebView, но во время тестов были проблемы, поэтому не советую).

- (instancetype)initWithScrollView:(UIScrollView *)scrollView;

Событие рефреша отлавливается с помощью делегирования, блоков и нотификаций:
Немного кода
Делегирование:
@property (assign, nonatomic) id<LGRefreshViewDelegate> delegate;

- (void)refreshViewRefreshing:(LGRefreshView *)refreshView;

Блоки:
@property (strong, nonatomic) void (^refreshHandler)(LGRefreshView *refreshView);

NSNotifications:
kLGRefreshViewBeginRefreshingNotification;
kLGRefreshViewEndRefreshingNotification;


Для завершения обновления предусмотрен метод:
- (void)endRefreshing;

Также рефреш можно вызвать программно:
- (void)triggerAnimated:(BOOL)animated;


Если у вас клиент-серверное приложение, то при переходе на новый контроллер часто приходится загружать данные. Чтобы не фризить UI, делать это нужно в дополнительном потоке. Но что показывать пользователю, пока происходит загрузка? А показывать можно разное, для этого и сделан LGPlaceholderView.

Что LGPlaceholderView может показывать:

  • Текст
  • UIActivityIndicatorView
  • Текст + UIActivityIndicatorView
  • UIProgressView
  • Текст + UIProgressView
  • Кастомную вьюху

Кроме того, LGPlaceholderView всегда будет находиться поверх других вьюх. Вы можете загрузить данные, подготовить их к показу, а потом анимированно скрыть LGPlaceholderView.

При инициализации нужно указать вьюху, которую вы будете скрывать:

- (instancetype)initWithView:(UIView *)view;

События можно отлавливать с помощью нотификаций:
kLGPlaceholderViewWillShowNotification;
kLGPlaceholderViewWillDismissNotification;
kLGPlaceholderViewDidShowNotification;
kLGPlaceholderViewDidDismissNotification;

Для показа предусмотрены различные методы, в зависимости от того, какой стиль вы хотите задать:
- (void)showActivityIndicatorAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showActivityIndicatorWithText:(NSString *)text animated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showProgressViewAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showProgressViewWithText:(NSString *)text animated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showText:(NSString *)text animated:(BOOL)animated completionHandler:(void(^)())completionHandler;
- (void)showView:(UIView *)view animated:(BOOL)animated completionHandler:(void(^)())completionHandler;

Чтобы скрыть placeholder, нужно вызвать:
- (void)dismissAnimated:(BOOL)animated completionHandler:(void(^)())completionHandler;

Кроме того, различные стили можно комбинировать, если вы вызовите несколько «show» методов подряд, то LGPlaceholderView сменится на другой.

Программное рисование изображений давно будоражит мой ум. Растровые картинки понемногу отмирают, даже apple в последних версиях xcode добавила поддержку для векторных изображений. Но что если не отягощать приложение дополнительными ресурсами, а рисовать изображения прямо внутри, благо инструменты позволяют.

Плюсы такого подхода, видящиеся мне:

  • Качество изображений всегда на высоте, так как не требуется масштабирование, каждый девайс рисует именно то что нужно ему;
  • Легкое и быстрое изменение содержимого (если, к примеру, необходимо поменять цвет, вместо того чтобы открывать редактор, загружать картинку, изменять цвет и сохранять, достаточно будет всего лишь поменять один параметр в коде);
  • Облегчение веса конечного архива с приложением.

Так и появился LGDrawer. Посмотрим, что он может рисовать на данный момент:
  • Прямоугольник (квадрат)
  • Эллипс (круг)
  • Треугольник
  • Плюс
  • Крест
  • Линия
  • Галочка
  • Стрелочка
  • Сердце
  • Звезда
  • Меню (3 параллельных линии с возможными точками)
  • Различные тени
  • Возможность накладывать изображения друг на друга, или вырезать одни изображения из других

Немного кода
#pragma mark - Rectangle

+ (UIImage *)drawRectangleWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
roundedCorners:(UIRectCorner)roundedCorners
cornerRadius:(CGFloat)cornerRadius
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
strokeType:(LGDrawerStrokeType)strokeType
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Ellipse

+ (UIImage *)drawEllipseWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
strokeType:(LGDrawerStrokeType)strokeType
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Triangle

/** Stroke type is center */
+ (UIImage *)drawTriangleWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
cornerRadius:(CGFloat)cornerRadius
direction:(LGDrawerDirection)direction
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Shadow

+ (UIImage *)drawShadowWithImageSize:(CGSize)imageSize
direction:(LGDrawerDirection)direction
backgroundColor:(UIColor *)backgroundColor
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Plus

+ (UIImage *)drawPlusWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
roundedCorners:(UIRectCorner)roundedCorners
cornerRadius:(CGFloat)cornerRadius
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
strokeType:(LGDrawerStrokeType)strokeType
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

+ (UIImage *)drawPlusWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
backgroundColor:(UIColor *)backgroundColor
color:(UIColor *)color
dash:(NSArray *)dash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Cross

+ (UIImage *)drawCrossWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
roundedCorners:(UIRectCorner)roundedCorners
cornerRadius:(CGFloat)cornerRadius
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
strokeType:(LGDrawerStrokeType)strokeType
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

+ (UIImage *)drawCrossWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
backgroundColor:(UIColor *)backgroundColor
color:(UIColor *)color
dash:(NSArray *)dash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Line

+ (UIImage *)drawLineWithImageSize:(CGSize)imageSize
length:(CGFloat)length
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
direction:(LGDrawerLineDirection)direction
backgroundColor:(UIColor *)backgroundColor
color:(UIColor *)color
dash:(NSArray *)dash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Tick

+ (UIImage *)drawTickWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
backgroundColor:(UIColor *)backgroundColor
color:(UIColor *)color
dash:(NSArray *)dash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Arrow

+ (UIImage *)drawArrowWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
direction:(LGDrawerDirection)direction
backgroundColor:(UIColor *)backgroundColor
color:(UIColor *)color
dash:(NSArray *)dash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

+ (UIImage *)drawArrowTailedWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
direction:(LGDrawerDirection)direction
backgroundColor:(UIColor *)backgroundColor
color:(UIColor *)color
dash:(NSArray *)dash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Heart

/** Stroke type is center */
+ (UIImage *)drawHeartWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Star

/** Stroke type is center */
+ (UIImage *)drawStarWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Menu

+ (UIImage *)drawMenuWithImageSize:(CGSize)imageSize
size:(CGSize)size
offset:(CGPoint)offset
rotate:(CGFloat)degrees
thickness:(CGFloat)thickness
dotted:(BOOL)dotted
dotsPosition:(LGDrawerMenuDotsPosition)dotsPosition
dotsCornerRadius:(CGFloat)dotsCornerRadius
linesCornerRadius:(CGFloat)linesCornerRadius
backgroundColor:(UIColor *)backgroundColor
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor
strokeThickness:(CGFloat)strokeThickness
strokeDash:(NSArray *)strokeDash
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGPoint)shadowOffset
shadowBlur:(CGFloat)shadowBlur;

#pragma mark - Images

+ (UIImage *)drawImage:(UIImage *)image1
onImage:(UIImage *)image2
clear:(BOOL)clear;

+ (UIImage *)drawImageOnImage:(NSArray *)images;

+ (UIImage *)drawImagesWithFinishSize:(CGSize)finishSize
image1:(UIImage *)image1
image1Rect:(CGRect)rect1
image2:(UIImage *)image2
image2Rect:(CGRect)rect2
clear:(BOOL)clear;

+ (UIImage *)drawImagesWithFinishSize:(CGSize)finishSize
image1:(UIImage *)image1
image1Offset:(CGPoint)offset1
image2:(UIImage *)image2
image2Offset:(CGPoint)offset2
clear:(BOOL)clear;


Принцип рисования следующий. У каждого метода есть параметры, где вы можете задавать размер области (холста), в которой будет находиться изображения, её заливку; размер самого изображения, его заливку, обводку, тень, смещение относительно центра внутри холста, угол поворота изображения и, если возможно, толщину линий и закругления углов.
Не все параметры удалось реализовать для каждого метода, но старался по максимуму возможного.

Было бы очень интересно, если сообщество помогло расширить возможности данной библиотеки. Во-первых, добавить недостающие параметры там, где этого не смог я. Во-вторых, увеличить каталог возможных изображений.



Часто хочется расширить функционал той или иной вьюхи. Почему для UILabel нельзя задать contentEdgeInsets? Если я хочу расположить UILabel поверх картинки, то для удобства чтения текста, вместо того, чтобы расширить background, приходится создавать дополнительную UIView. Или для UIButton, почему для каждого состояния можно задать текст, цвета текста, картинку, картинку background'a, но банального цвета background'a задать нельзя. А что насчет выбора расположения картинки относительно текста?

В общем, думаю, вы поняли направление моих мыслей, в этой библиотеке я написал классы, которые расширяют возможности стандартных вьюх:

  • contentEdgeInsets для UILabel
  • backgroundColor для разных состояний UIButton
  • Возможность задать UIButton не прямоугольную форму за счет масок
  • Выбрать расположение картинки относительно текста в UIButton
  • Задание максимальной длины текста для UITextField и UITextView
  • contentEdgeInsets для текста и боковых изображений в UITextField
  • Удаление лишних пробелов и переносов строк из UITextField
  • Возможность авторасширения для UITextView с заданием максимальной высоты или количества строк
  • placeholder для UITextView


Так же обстоят дела и с UIViewController'ами. Но тут расширять возможности было сложнее и не так очевидно, поэтому не считаю, что смог в полной мере добиться желаемого результата.

Краткий список:

  • Добавил UIScrollViewController и UIWebViewController
  • Для UITableViewController, UICollectionViewController и UIScrollViewController добавил LGRefreshView и LGPlaceholderView; для UIWebViewController только LGPlaceholderView
  • Для UITableViewControlle добавил новый метод делегата «heightForRowAtIndexPathAsync», который позволяет асинхронно рассчитывать высоту ячеек. То есть, если у вас динамическая высота ячеек и вы часто подгружаете список, данный метод поможет избежать задержек в интерфейсе
  • Для UICollectionViewController попытался упростить инициализацию layout'а, чтобы можно было настроить сетку без лишних рассчетов
  • UIWebViewController можно использовать без наследования, а сразу передавать в тело ссылку на нужный ресурс при инициализации
  • Добавил методы для автоматического слежения за клавиатурой, то есть contentInsets будут меняться при появлении и исчезновении клавиатуры


Каждый день мы решаем огромное количество повторяющихся задач. Большинство из них мы помним, но часто мелкие детали реализации ускользают и приходится периодически освежать их в памяти. Данные хелперы — это библиотеки, содержащие в себе всевозможные макроссы и методы, помогающие в повседневной жизни. Я собрал здесь довольно много полезной информации, не обязательно даже использовать их в своих проектах, иногда полезно бывает просто заглянуть, чтобы вспомнить ту или иную фичу.

Обработка изображений, конвертация цветов, использование масок, отправка электронных писем, вызов звонка с подтверждением и без, показ местоположения на карте, узнать состояние подключения к интернету, добавить человека в адресную книгу, закодировать данные, получить MD5 и SHA1 хэши, добавить событие в календарь, получить изображение с камеры… и многое многое другое, что я не буду здесь перечислять. Думаю названия всех методов и переменных должны быть более-менее интуитивно понятны, поэтому, чтобы ознакомиться с полным списком возможностей, предлагаю просмотреть header файлы исходников.


Эти 3 библиотеки, на мой взгляд, не так интересны как остальные. LGSharing ясное дело помогает постить в социальные сети (ВКонтакте, Facebook и Twitter) + отправлять сообщения на email и sms. LGConnection является оберткой вокруг AFNetworking, из коробки может парсить ответ от сервера включая XML формат + имеет логику для обработки прерывания интернет соединения. LGAudioStreamHelper помогает работать с аудио стримами, определять формат, получать метаданные и записывать поток. Более подробно рассказывать не буду, если кому интересно, заходите на гитхаб и пробуйте.

Всем спасибо за внимание. Буду очень рад если мои труды кому-то принесут пользу. Открыт для объективной критики, предложениям по улучшению или расширению функционала библиотек. Если есть какие вопросы — задавайте, постараюсь ответить.