Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,14 @@
extension Bundle {
func load<T:UIView>(viewClass: T.Type, owner: Any? = nil, options: [AnyHashable: Any]? = nil) -> T? {
return loadNibNamed(String(describing: viewClass), owner: owner, options: options as? [UINib.OptionsKey : Any])?.first as? T
}
@objc func load(viewClass: AnyClass, owner: Any? = nil, options: [AnyHashable: Any]? = nil) -> [Any]? {
return loadNibNamed(toString(viewClass), owner: owner, options: options as? [UINib.OptionsKey : Any])
}
@objc func load(plist: String) -> Dictionary<String, AnyObject>? {
guard let path = Bundle.main.path(forResource: plist, ofType: "plist") else { return nil }
return NSDictionary(contentsOfFile: path) as? [String: AnyObject]
}
}

View file

@ -0,0 +1,16 @@
extension CALayer {
func setCornerRadius(_ cornerRadius: CornerRadius,
maskedCorners: CACornerMask? = nil) {
self.cornerRadius = cornerRadius.value
if let maskedCorners {
self.maskedCorners = maskedCorners
}
cornerCurve = .continuous
}
}
extension CACornerMask {
static var all: CACornerMask {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
}

View file

@ -0,0 +1,7 @@
#include "geometry/point2d.hpp"
@interface CLLocation (Mercator)
- (m2::PointD)mercator;
@end

View file

@ -0,0 +1,8 @@
#import "CLLocation+Mercator.h"
#import "MWMLocationHelpers.h"
@implementation CLLocation (Mercator)
- (m2::PointD)mercator { return location_helpers::ToMercator(self.coordinate); }
@end

View file

@ -0,0 +1,19 @@
import Foundation
extension BookmarkGroup {
@objc func placesCountTitle() -> String {
let bookmarks = String(format: L("bookmarks_places"), bookmarksCount)
let tracks = String(format: L("tracks"), trackCount)
if (bookmarksCount == 0 && trackCount == 0) || (bookmarksCount > 0 && trackCount > 0) {
return "\(bookmarks), \(tracks)"
}
if (bookmarksCount > 0) {
return bookmarks
}
return tracks
}
}

View file

@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#include "geometry/point2d.hpp"
@interface MWMMapViewControlsManager (AddPlace)
- (void)addPlace:(BOOL)isBusiness position:(nullable m2::PointD const *)optionalPosition;
@end

View file

@ -0,0 +1,100 @@
extension NSAttributedString {
@objc
public class func string(withHtml htmlString:String, defaultAttributes attributes:[NSAttributedString.Key : Any]) -> NSAttributedString? {
guard let data = htmlString.data(using: .utf8) else { return nil }
guard let text = try? NSMutableAttributedString(data: data,
options: [.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil) else { return nil }
text.addAttributes(attributes, range: NSMakeRange(0, text.length))
return text
}
}
extension NSMutableAttributedString {
@objc convenience init?(htmlString: String, baseFont: UIFont, paragraphStyle: NSParagraphStyle?, estimatedWidth: CGFloat = 0) {
self.init(htmlString: htmlString, baseFont: baseFont)
if let paragraphStyle = paragraphStyle {
addAttribute(.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, length))
}
guard estimatedWidth > 0 else { return }
enumerateAttachments(estimatedWidth: estimatedWidth)
}
@objc convenience init?(htmlString: String, baseFont: UIFont) {
guard let data = htmlString.data(using: .utf8) else { return nil }
do {
try self.init(data: data,
options: [.documentType : NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
} catch {
return nil
}
enumerateFont(baseFont)
}
@objc convenience init?(htmlString: String, baseFont: UIFont, estimatedWidth: CGFloat) {
guard let data = htmlString.data(using: .utf8) else { return nil }
do {
try self.init(data: data,
options: [.documentType : NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
} catch {
return nil
}
enumerateFont(baseFont)
guard estimatedWidth > 0 else { return }
enumerateAttachments(estimatedWidth: estimatedWidth)
}
func enumerateFont(_ baseFont: UIFont) {
enumerateAttribute(.font, in: NSMakeRange(0, length), options: []) { (value, range, _) in
if let font = value as? UIFont,
let descriptor = baseFont.fontDescriptor.withSymbolicTraits(font.fontDescriptor.symbolicTraits) {
let newFont = UIFont(descriptor: descriptor, size: baseFont.pointSize)
addAttribute(.font, value: newFont, range: range)
} else {
addAttribute(.font, value: baseFont, range: range)
}
}
}
func enumerateAttachments(estimatedWidth: CGFloat) {
enumerateAttribute(.attachment, in: NSMakeRange(0, length), options: []) { (value, range, _) in
if let attachement = value as? NSTextAttachment,
let image = attachement.image(forBounds: attachement.bounds, textContainer: NSTextContainer(), characterIndex: range.location) {
if image.size.width > estimatedWidth || attachement.bounds.width > estimatedWidth {
let resizedAttachment = NSTextAttachment()
if image.size.width > estimatedWidth {
let newImage = resizeImage(image: image, scale: estimatedWidth/image.size.width) ?? image
resizedAttachment.image = newImage
} else {
resizedAttachment.image = image
}
addAttribute(.attachment, value: resizedAttachment, range: range)
}
}
}
}
func resizeImage(image: UIImage, scale: CGFloat) -> UIImage? {
let newSize = CGSize(width: image.size.width*scale, height: image.size.height*scale)
let rect = CGRect(origin: CGPoint.zero, size: newSize)
let renderer = UIGraphicsImageRenderer(size: newSize)
let newImage = renderer.image { context in
image.draw(in: rect)
}
return newImage
}
}

View file

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSDate (TimeDistance)
- (NSInteger)daysToNow;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,14 @@
#import "NSDate+TimeDistance.h"
@implementation NSDate (TimeDistance)
- (NSInteger)daysToNow {
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay
fromDate:self
toDate:[NSDate date]
options:NSCalendarWrapComponents];
return components.day;
}
@end

View file

@ -0,0 +1,11 @@
@interface NSString (MapsMeSize)
- (CGSize)sizeWithDrawSize:(CGSize)size font:(UIFont *)font;
@end
@interface NSString (MapsMeRanges)
- (NSArray<NSValue *> *)rangesOfString:(NSString *)aString;
@end

View file

@ -0,0 +1,36 @@
#import "NSString+Categories.h"
@implementation NSString (MapsMeSize)
- (CGSize)sizeWithDrawSize:(CGSize)drawSize font:(UIFont *)font {
CGRect rect = [self boundingRectWithSize:drawSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
return CGRectIntegral(rect).size;
}
@end
@implementation NSString (MapsMeRanges)
- (NSArray<NSValue *> *)rangesOfString:(NSString *)aString {
NSMutableArray *result = [NSMutableArray array];
if (self.length == 0) {
return [result copy];
}
NSRange searchRange = NSMakeRange(0, self.length);
while (searchRange.location < self.length) {
searchRange.length = self.length - searchRange.location;
NSRange foundRange = [self rangeOfString:aString options:NSCaseInsensitiveSearch range:searchRange];
if (foundRange.location == NSNotFound) {
break;
}
searchRange.location = foundRange.location + foundRange.length;
[result addObject:[NSValue valueWithRange:foundRange]];
}
return result;
}
@end

View file

@ -0,0 +1,29 @@
extension String {
func size(width: CGFloat, font: UIFont, maxNumberOfLines: Int = 0) -> CGSize {
if isEmpty {
return CGSize.zero
}
let lineHeight = font.lineHeight
let maximumHeight = maxNumberOfLines == 0 ? CGFloat.greatestFiniteMagnitude : lineHeight * CGFloat(maxNumberOfLines + 1)
let constraintSize = CGSize(width: width, height: maximumHeight)
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine]
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = .byWordWrapping
paragraph.alignment = .natural
let attributes = [
NSAttributedString.Key.font: font,
NSAttributedString.Key.paragraphStyle: paragraph,
]
var rect = (self as NSString).boundingRect(with: constraintSize, options: options, attributes: attributes, context: nil)
var numberOfLines = ceil(rect.height / lineHeight)
if maxNumberOfLines != 0 {
if width - rect.width < font.ascender {
rect.size.width = width - font.ascender
numberOfLines += 1
}
numberOfLines = min(numberOfLines, CGFloat(maxNumberOfLines))
}
return CGSize(width: ceil(rect.width), height: ceil(numberOfLines * lineHeight))
}
}

View file

@ -0,0 +1,28 @@
extension UIApplication {
private static let overlayViewController = LoadingOverlayViewController()
@objc
func showLoadingOverlay(completion: (() -> Void)? = nil) {
guard let window = (self.connectedScenes.filter { $0.activationState == .foregroundActive }.first(where: { $0 is UIWindowScene }) as? UIWindowScene)?.windows.first(where: { $0.isKeyWindow }) else {
completion?()
return
}
DispatchQueue.main.async {
UIApplication.overlayViewController.modalPresentationStyle = .overFullScreen
UIApplication.overlayViewController.modalTransitionStyle = .crossDissolve
if window.rootViewController?.presentedViewController != nil {
window.rootViewController?.presentedViewController?.present(UIApplication.overlayViewController, animated: true, completion: completion)
} else {
window.rootViewController?.present(UIApplication.overlayViewController, animated: true, completion: completion)
}
}
}
@objc
func hideLoadingOverlay(completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.overlayViewController.dismiss(animated: true, completion: completion)
}
}
}

View file

@ -0,0 +1,12 @@
extension UIButton {
@objc func setImagePadding(_ padding: CGFloat) {
let isRightToLeft = UIView.userInterfaceLayoutDirection(for: self.semanticContentAttribute) == .rightToLeft
if isRightToLeft {
self.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: padding)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: -2 * padding)
} else {
self.contentEdgeInsets = UIEdgeInsets(top: 0, left: padding, bottom: 0, right: 0)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -2 * padding, bottom: 0, right: 0)
}
}
}

View file

@ -0,0 +1,5 @@
@interface UIButton (Orientation)
- (void)matchInterfaceOrientation;
@end

View file

@ -0,0 +1,13 @@
#import "UIButton+Orientation.h"
#import <CoreApi/MWMCommon.h>
@implementation UIButton (Orientation)
- (void)matchInterfaceOrientation
{
if (isInterfaceRightToLeft())
self.imageView.transform = CGAffineTransformMakeScale(-1, 1);
}
@end

View file

@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>
@interface UIButton (RuntimeAttributes)
@property(copy, nonatomic) NSString * localizedText;
@end

View file

@ -0,0 +1,18 @@
#import <objc/runtime.h>
#import "UIButton+RuntimeAttributes.h"
@implementation UIButton (RuntimeAttributes)
- (void)setLocalizedText:(NSString *)localizedText
{
[self setTitle:L(localizedText) forState:UIControlStateNormal];
[self setTitle:L(localizedText) forState:UIControlStateDisabled];
}
- (NSString *)localizedText
{
NSString * title = [self titleForState:UIControlStateNormal];
return L(title);
}
@end

View file

@ -0,0 +1,17 @@
extension UICollectionView {
@objc func register(cellClass: AnyClass) {
register(UINib(cellClass), forCellWithReuseIdentifier: toString(cellClass))
}
@objc func dequeueReusableCell(withCellClass cellClass: AnyClass, indexPath: IndexPath) -> UICollectionViewCell {
return dequeueReusableCell(withReuseIdentifier: toString(cellClass), for: indexPath)
}
func register<Cell>(cell: Cell.Type) where Cell: UICollectionViewCell {
register(cell, forCellWithReuseIdentifier: toString(cell))
}
func dequeueReusableCell<Cell>(cell: Cell.Type, indexPath: IndexPath) -> Cell where Cell: UICollectionViewCell {
return dequeueReusableCell(withReuseIdentifier: toString(cell), for: indexPath) as! Cell
}
}

View file

@ -0,0 +1,42 @@
#import "UIColor+PartnerColor.h"
NS_ASSUME_NONNULL_BEGIN
@interface UIColor (MapsMeColor)
+ (UIColor *)black;
+ (UIColor *)blackPrimaryText;
+ (UIColor *)blackSecondaryText;
+ (UIColor *)blackHintText;
+ (UIColor *)red;
+ (UIColor *)white;
+ (UIColor *)primary;
+ (UIColor *)pressBackground;
+ (UIColor *)linkBlue;
+ (UIColor *)linkBlueHighlighted;
+ (UIColor *)buttonRed;
+ (UIColor *)blackDividers;
+ (UIColor *)whitePrimaryText;
+ (UIColor *)whitePrimaryTextHighlighted;
+ (UIColor *)whiteHintText;
+ (UIColor *)buttonDisabledBlueText;
+ (UIColor *)blackOpaque;
+ (UIColor *)bookingBackground;
+ (UIColor *)opentableBackground;
+ (UIColor *)transparentGreen;
+ (UIColor *)speedLimitRed;
+ (UIColor *)speedLimitGreen;
+ (UIColor *)speedLimitWhite;
+ (UIColor *)speedLimitLightGray;
+ (UIColor *)speedLimitDarkGray;
+ (UIColor *)carplayPlaceholderBackground;
+ (UIColor *)colorWithName:(NSString *)colorName;
+ (UIColor *)colorFromHexString:(NSString *)hexString;
+ (void)setNightMode:(BOOL)mode;
+ (BOOL)isNightMode;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,179 @@
#import "UIColorRoutines.h"
#import "UIColor+MapsMeColor.h"
#import "SwiftBridge.h"
static BOOL isNightMode = NO;
@implementation UIColor (MapsMeColor)
// hex string without #
+ (UIColor *)colorFromHexString:(NSString *)hexString
{
unsigned rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:hexString];
[scanner setScanLocation:0];
[scanner scanHexInt:&rgbValue];
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
}
+ (void)setNightMode:(BOOL)mode
{
isNightMode = mode;
}
+ (BOOL)isNightMode
{
return isNightMode;
}
// Green color
+ (UIColor *)primary
{
return StyleManager.shared.theme.colors.primary;
}
// Use for opaque fullscreen
+ (UIColor *)fadeBackground
{
return [UIColor colorWithWhite:0. alpha:alpha80];
}
// Background color && press color
+ (UIColor *)pressBackground
{
return StyleManager.shared.theme.colors.pressBackground;
}
// Red color (use for status closed in place page)
+ (UIColor *)red
{
return StyleManager.shared.theme.colors.red;
}
// Blue color (use for links and phone numbers)
+ (UIColor *)linkBlue
{
return StyleManager.shared.theme.colors.linkBlue;
}
+ (UIColor *)linkBlueHighlighted
{
return StyleManager.shared.theme.colors.linkBlueHighlighted;
}
+ (UIColor *)linkBlueDark
{
return StyleManager.shared.theme.colors.linkBlueDark;
}
+ (UIColor *)buttonRed
{
return StyleManager.shared.theme.colors.buttonRed;
}
+ (UIColor *)black
{
return StyleManager.shared.theme.colors.black;
}
+ (UIColor *)blackPrimaryText
{
return StyleManager.shared.theme.colors.blackPrimaryText;
}
+ (UIColor *)blackSecondaryText
{
return StyleManager.shared.theme.colors.blackSecondaryText;
}
+ (UIColor *)blackHintText
{
return StyleManager.shared.theme.colors.blackHintText;
}
+ (UIColor *)blackDividers
{
return StyleManager.shared.theme.colors.blackDividers;
}
+ (UIColor *)white
{
return StyleManager.shared.theme.colors.white;
}
+ (UIColor *)whitePrimaryText
{
return [UIColor colorWithWhite:1. alpha:alpha87];
}
+ (UIColor *)whitePrimaryTextHighlighted
{
// use only for highlighted colors!
return [UIColor colorWithWhite:1. alpha:alpha30];
}
+ (UIColor *)whiteHintText
{
return StyleManager.shared.theme.colors.whiteHintText;
}
+ (UIColor *)buttonDisabledBlueText
{
return StyleManager.shared.theme.colors.buttonDisabledBlueText;
}
+ (UIColor *)buttonHighlightedBlueText
{
return [UIColor colorWithRed:scaled(3.) green:scaled(122.) blue:scaled(255.) alpha:alpha54];
}
+ (UIColor *)blackOpaque
{
return StyleManager.shared.theme.colors.blackOpaque;
}
+ (UIColor *)carplayPlaceholderBackground
{
return StyleManager.shared.theme.colors.carplayPlaceholderBackground;
}
+ (UIColor *)bookingBackground
{
return [UIColor colorWithRed:scaled(25.) green:scaled(69.) blue:scaled(125.) alpha:alpha100];
}
+ (UIColor *)opentableBackground
{
return [UIColor colorWithRed:scaled(218.) green:scaled(55) blue:scaled(67) alpha:alpha100];
}
+ (UIColor *)transparentGreen
{
return [UIColor colorWithRed:scaled(233) green:scaled(244) blue:scaled(233) alpha:alpha26];
}
+ (UIColor *)colorWithName:(NSString *)colorName
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [[UIColor class] performSelector:NSSelectorFromString(colorName)];
#pragma clang diagnostic pop
}
+ (UIColor *)speedLimitRed {
return [UIColor colorWithRed:scaled(224) green:scaled(31) blue:scaled(31) alpha:alpha100];
}
+ (UIColor *)speedLimitGreen {
return [UIColor colorWithRed:scaled(1) green:scaled(104) blue:scaled(44) alpha:alpha100];
}
+ (UIColor *)speedLimitWhite {
return [UIColor colorWithRed:scaled(255) green:scaled(255) blue:scaled(255) alpha:alpha80];
}
+ (UIColor *)speedLimitLightGray {
return [UIColor colorWithRed:scaled(0) green:scaled(0) blue:scaled(0) alpha:alpha20];
}
+ (UIColor *)speedLimitDarkGray {
return [UIColor colorWithRed:scaled(51) green:scaled(51) blue:scaled(50) alpha:alpha100];
}
@end

View file

@ -0,0 +1,63 @@
extension UIColor {
func blending(with color: UIColor) -> UIColor {
var bgR: CGFloat = 0
var bgG: CGFloat = 0
var bgB: CGFloat = 0
var bgA: CGFloat = 0
var fgR: CGFloat = 0
var fgG: CGFloat = 0
var fgB: CGFloat = 0
var fgA: CGFloat = 0
self.getRed(&bgR, green: &bgG, blue: &bgB, alpha: &bgA)
color.getRed(&fgR, green: &fgG, blue: &fgB, alpha: &fgA)
let r = fgA * fgR + (1 - fgA) * bgR
let g = fgA * fgG + (1 - fgA) * bgG
let b = fgA * fgB + (1 - fgA) * bgB
return UIColor(red: r, green: g, blue: b, alpha: bgA)
}
func lighter(percent: CGFloat) -> UIColor {
return colorWithBrightnessFactor(factor: 1 + percent)
}
func darker(percent: CGFloat) -> UIColor {
return colorWithBrightnessFactor(factor: 1 - percent)
}
private func colorWithBrightnessFactor(factor: CGFloat) -> UIColor {
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
if getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
return UIColor(hue: hue, saturation: saturation, brightness: brightness * factor, alpha: alpha)
} else {
return self
}
}
}
extension UIColor {
func components() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
return getRed(&r, green: &g, blue: &b, alpha: &a) ? (r,g,b,a) : nil
}
static func intermediateColor( color1: UIColor, color2: UIColor, _ scale: CGFloat) -> UIColor? {
guard let comp1 = color1.components(),
let comp2 = color2.components() else {
return nil
}
let scale = min(1, max(0, scale))
let r = comp1.red + (comp2.red - comp1.red) * scale
let g = comp1.green + (comp2.green - comp1.green) * scale
let b = comp1.blue + (comp2.blue - comp1.blue) * scale
let a = comp1.alpha + (comp2.alpha - comp1.alpha) * scale
return UIColor(red: r, green: g, blue: b, alpha: a)
}
}

View file

@ -0,0 +1,18 @@
// This file is autogenerated
@interface UIColor (PartnerColor)
+ (UIColor *)partner1Background;
+ (UIColor *)partner1TextColor;
+ (UIColor *)partner2Background;
+ (UIColor *)partner2TextColor;
+ (UIColor *)partner3Background;
+ (UIColor *)partner3TextColor;
+ (UIColor *)partner18Background;
+ (UIColor *)partner18TextColor;
+ (UIColor *)partner19Background;
+ (UIColor *)partner19TextColor;
+ (UIColor *)partner20Background;
+ (UIColor *)partner20TextColor;
@end

View file

@ -0,0 +1,56 @@
// This file is autogenerated
#import "UIColorRoutines.h"
#import "UIColor+PartnerColor.h"
@implementation UIColor (PartnerColor)
+ (UIColor *)partner1Background
{
return [UIColor colorWithRed:scaled(48) green:scaled(52) blue:scaled(56) alpha:1];
}
+ (UIColor *)partner1TextColor
{
return [UIColor colorWithRed:scaled(255) green:scaled(255) blue:scaled(255) alpha:1];
}
+ (UIColor *)partner2Background
{
return [UIColor colorWithRed:scaled(215) green:scaled(215) blue:scaled(215) alpha:1];
}
+ (UIColor *)partner2TextColor
{
return [UIColor colorWithRed:scaled(33) green:scaled(33) blue:scaled(33) alpha:1];
}
+ (UIColor *)partner3Background
{
return [UIColor colorWithRed:scaled(230) green:scaled(23) blue:scaled(23) alpha:1];
}
+ (UIColor *)partner3TextColor
{
return [UIColor colorWithRed:scaled(255) green:scaled(255) blue:scaled(255) alpha:1];
}
+ (UIColor *)partner18Background
{
return [UIColor colorWithRed:scaled(0) green:scaled(185) blue:scaled(86) alpha:100];
}
+ (UIColor *)partner18TextColor
{
return [UIColor colorWithRed:scaled(255) green:scaled(255) blue:scaled(255) alpha:100];
}
+ (UIColor *)partner19Background
{
return [UIColor colorWithRed:scaled(87) green:scaled(26) blue:scaled(140) alpha:100];
}
+ (UIColor *)partner19TextColor
{
return [UIColor colorWithRed:scaled(255) green:scaled(255) blue:scaled(255) alpha:100];
}
+ (UIColor *)partner20Background
{
return [UIColor colorWithRed:scaled(87) green:scaled(26) blue:scaled(140) alpha:100];
}
+ (UIColor *)partner20TextColor
{
return [UIColor colorWithRed:scaled(255) green:scaled(255) blue:scaled(255) alpha:100];
}
@end

View file

@ -0,0 +1,16 @@
static CGFloat const alpha04 = 0.04;
static CGFloat const alpha12 = 0.12;
static CGFloat const alpha20 = 0.20;
static CGFloat const alpha26 = 0.26;
static CGFloat const alpha30 = 0.3;
static CGFloat const alpha32 = 0.32;
static CGFloat const alpha36 = 0.36;
static CGFloat const alpha40 = 0.4;
static CGFloat const alpha54 = 0.54;
static CGFloat const alpha70 = 0.7;
static CGFloat const alpha80 = 0.8;
static CGFloat const alpha87 = 0.87;
static CGFloat const alpha90 = 0.9;
static CGFloat const alpha100 = 1.;
static inline CGFloat scaled(CGFloat f) { return f / 255.; };

View file

@ -0,0 +1,29 @@
NS_ASSUME_NONNULL_BEGIN
@interface UIFont (MapsMeFonts)
+ (UIFont *)regular10;
+ (UIFont *)regular12;
+ (UIFont *)regular13;
+ (UIFont *)regular14;
+ (UIFont *)regular16;
+ (UIFont *)regular17;
+ (UIFont *)regular18;
+ (UIFont *)regular24;
+ (UIFont *)regular32;
+ (UIFont *)regular52;
+ (UIFont *)medium10;
+ (UIFont *)medium14;
+ (UIFont *)medium16;
+ (UIFont *)medium17;
+ (UIFont *)light12;
+ (UIFont *)bold12;
+ (UIFont *)bold14;
+ (UIFont *)bold16;
+ (UIFont *)bold17;
+ (UIFont *)bold24;
+ (UIFont *)bold28;
+ (UIFont *)bold36;
+ (UIFont *)semibold16;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,27 @@
@implementation UIFont (MapsMeFonts)
+ (UIFont *)regular10 { return [UIFont systemFontOfSize:10]; }
+ (UIFont *)regular12 { return [UIFont systemFontOfSize:12]; }
+ (UIFont *)regular13 { return [UIFont systemFontOfSize:13]; }
+ (UIFont *)regular14 { return [UIFont systemFontOfSize:14]; }
+ (UIFont *)regular16 { return [UIFont systemFontOfSize:16]; }
+ (UIFont *)regular17 { return [UIFont systemFontOfSize:17]; }
+ (UIFont *)regular18 { return [UIFont systemFontOfSize:18]; }
+ (UIFont *)regular24 { return [UIFont systemFontOfSize:24]; }
+ (UIFont *)regular32 { return [UIFont systemFontOfSize:32]; }
+ (UIFont *)regular52 { return [UIFont systemFontOfSize:52]; }
+ (UIFont *)medium10 { return [UIFont systemFontOfSize:10 weight:UIFontWeightMedium]; }
+ (UIFont *)medium14 { return [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; }
+ (UIFont *)medium16 { return [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; }
+ (UIFont *)medium17 { return [UIFont systemFontOfSize:17 weight:UIFontWeightMedium]; }
+ (UIFont *)light12 { return [UIFont systemFontOfSize:12 weight:UIFontWeightLight]; }
+ (UIFont *)bold12 { return [UIFont boldSystemFontOfSize:12]; }
+ (UIFont *)bold14 { return [UIFont boldSystemFontOfSize:14]; }
+ (UIFont *)bold16 { return [UIFont boldSystemFontOfSize:16]; }
+ (UIFont *)bold17 { return [UIFont boldSystemFontOfSize:17]; }
+ (UIFont *)bold24 { return [UIFont boldSystemFontOfSize:24]; }
+ (UIFont *)bold28 { return [UIFont boldSystemFontOfSize:28]; }
+ (UIFont *)bold36 { return [UIFont boldSystemFontOfSize:26]; }
+ (UIFont *)semibold16 { return [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; }
@end

View file

@ -0,0 +1,9 @@
extension UIImage {
static func filled(with color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
color.setFill()
context.fill(CGRect(origin: .zero, size: size))
}
}
}

View file

@ -0,0 +1,5 @@
@interface UIImage (RGBAData)
+ (UIImage *)imageWithRGBAData:(NSData *)data width:(size_t)width height:(size_t)height;
@end

View file

@ -0,0 +1,36 @@
#import "UIImage+RGBAData.h"
static void releaseCallback(void *info, const void *data, size_t size) {
CFRelease((CFDataRef)info);
}
@implementation UIImage (RGBAData)
+ (UIImage *)imageWithRGBAData:(NSData *)data width:(size_t)width height:(size_t)height {
size_t bytesPerPixel = 4;
size_t bitsPerComponent = 8;
size_t bitsPerPixel = bitsPerComponent * bytesPerPixel;
size_t bytesPerRow = bytesPerPixel * width;
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CFDataRef cfData = (__bridge_retained CFDataRef)data;
CGDataProviderRef provider = CGDataProviderCreateWithData((void *)cfData,
data.bytes,
height * bytesPerRow,
releaseCallback);
CGImageRef cgImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow,
colorSpace, bitmapInfo, provider,
NULL, YES, kCGRenderingIntentDefault);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(provider);
CGImageRelease(cgImage);
return image;
}
@end

View file

@ -0,0 +1,17 @@
typedef NS_ENUM(NSUInteger, MWMImageColoring) {
MWMImageColoringOther,
MWMImageColoringBlue,
MWMImageColoringBlack,
MWMImageColoringWhite,
MWMImageColoringGray,
MWMImageColoringSeparator
};
@interface UIImageView (Coloring)
@property(nonatomic) MWMImageColoring mwm_coloring;
@property(copy, nonatomic) NSString * mwm_name;
- (void)changeColoringToOpposite;
@end

View file

@ -0,0 +1,101 @@
#import "UIImageView+Coloring.h"
#import <objc/runtime.h>
@implementation UIImageView (Coloring)
- (void)setMwm_name:(NSString *)mwm_name
{
objc_setAssociatedObject(self, @selector(mwm_name), mwm_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.image =
[UIImage imageNamed:[NSString stringWithFormat:@"%@_%@", mwm_name,
[UIColor isNightMode] ? @"dark" : @"light"]];
}
- (NSString *)mwm_name { return objc_getAssociatedObject(self, @selector(mwm_name)); }
- (void)setMwm_coloring:(MWMImageColoring)mwm_coloring
{
objc_setAssociatedObject(self, @selector(mwm_coloring), @(mwm_coloring),
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (mwm_coloring == MWMImageColoringOther)
return;
[self applyColoring];
}
- (MWMImageColoring)mwm_coloring
{
return [objc_getAssociatedObject(self, @selector(mwm_coloring)) integerValue];
}
- (void)applyColoring
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
self.tintColor = [[UIColor class] performSelector:self.coloringSelector];
#pragma clang diagnostic pop
}
- (void)changeColoringToOpposite
{
if (self.mwm_coloring == MWMImageColoringOther)
{
if (self.mwm_name)
self.image = [UIImage
imageNamed:[NSString stringWithFormat:@"%@_%@", self.mwm_name,
[UIColor isNightMode] ? @"dark" : @"light"]];
return;
}
[self applyColoring];
}
- (SEL)coloringSelector
{
switch (self.mwm_coloring)
{
case MWMImageColoringWhite: return @selector(white);
case MWMImageColoringBlack: return @selector(blackSecondaryText);
case MWMImageColoringBlue: return @selector(linkBlue);
case MWMImageColoringGray: return @selector(blackHintText);
case MWMImageColoringOther: return @selector(white);
case MWMImageColoringSeparator: return @selector(blackDividers);
}
}
- (void)setHighlighted:(BOOL)highlighted
{
switch (self.mwm_coloring)
{
case MWMImageColoringWhite:
self.tintColor = highlighted ? [UIColor whiteHintText] : [UIColor white];
break;
case MWMImageColoringBlack:
self.tintColor = highlighted ? [UIColor blackHintText] : [UIColor blackSecondaryText];
break;
case MWMImageColoringGray:
self.tintColor = highlighted ? [UIColor blackSecondaryText] : [UIColor blackHintText];
break;
case MWMImageColoringOther:
case MWMImageColoringBlue:
case MWMImageColoringSeparator: break;
}
}
- (void)setColoring:(NSString *)coloring
{
if ([coloring isEqualToString:@"MWMBlue"])
self.mwm_coloring = MWMImageColoringBlue;
else if ([coloring isEqualToString:@"MWMBlack"])
self.mwm_coloring = MWMImageColoringBlack;
else if ([coloring isEqualToString:@"MWMOther"])
self.mwm_coloring = MWMImageColoringOther;
else if ([coloring isEqualToString:@"MWMGray"])
self.mwm_coloring = MWMImageColoringGray;
else if ([coloring isEqualToString:@"MWMSeparator"])
self.mwm_coloring = MWMImageColoringSeparator;
else if ([coloring isEqualToString:@"MWMWhite"])
self.mwm_coloring = MWMImageColoringWhite;
else
NSAssert(false, @"Incorrect UIImageView's coloring");
}
@end

View file

@ -0,0 +1,89 @@
#import <CoreApi/MWMTypes.h>
static inline CGPoint SubtractCGPoint(CGPoint p1, CGPoint p2)
{
return CGPointMake(p1.x - p2.x, p1.y - p2.y);
}
static inline CGPoint AddCGPoint(CGPoint p1, CGPoint p2)
{
return CGPointMake(p1.x + p2.x, p1.y + p2.y);
}
static inline CGPoint MultiplyCGPoint(CGPoint point, CGFloat multiplier)
{
return CGPointMake(point.x * multiplier, point.y * multiplier);
}
static inline CGFloat LengthCGPoint(CGPoint point)
{
return (CGFloat)sqrt(point.x * point.x + point.y * point.y);
}
@interface NSObject (Optimized)
+ (NSString *)className;
- (void)performAfterDelay:(NSTimeInterval)delayInSec block:(MWMVoidBlock)block;
@end
@interface UIView (Coordinates)
@property (nonatomic) CGFloat minX;
@property (nonatomic) CGFloat minY;
@property (nonatomic) CGFloat midX;
@property (nonatomic) CGFloat midY;
@property (nonatomic) CGFloat maxX;
@property (nonatomic) CGFloat maxY;
@property (nonatomic) CGPoint origin;
@property (nonatomic) CGFloat width;
@property (nonatomic) CGFloat height;
@property (nonatomic) CGSize size;
- (void)sizeToIntegralFit;
@end
@interface UIView (Refresh)
@end
@interface UIApplication (URLs)
- (void)rateApp;
@end
@interface SolidTouchView : UIView
@end
@interface SolidTouchImageView : UIImageView
@end
@interface UIViewController (Safari)
/// Open URL internally in SFSafariViewController. Returns NO (false) if the url id invalid.
- (BOOL)openUrl:(NSString *)urlString;
/// Open URL externally in installed application (or in Safari if there are no appropriate application) if possible or internally in SFSafariViewController. Returns NO (false) if the url id invalid.
///
/// @param urlString: URL string to open.
/// @param externally: If true, try to open URL in installed application or in Safari, otherwise open in internal browser without leaving the app.
- (BOOL)openUrl:(NSString *)urlString externally:(BOOL)externally;
/// Open URL externally in installed application (or in Safari if there are no appropriate application) if possible or internally in SFSafariViewController. Returns NO (false) if the url id invalid.
///
/// @param urlString: URL string to open.
/// @param externally: If true, try to open URL in installed application or in Safari, otherwise open in internal browser without leaving the app.
/// @param skipEncoding: If true, extra URL encoding will be skipped
- (BOOL)openUrl:(NSString *)urlString externally:(BOOL)externally skipEncoding:(BOOL)skipEncoding;
@end
@interface UIImage (ImageWithColor)
+ (UIImage *)imageWithColor:(UIColor *)color;
@end

View file

@ -0,0 +1,249 @@
#import "UIKitCategories.h"
#import "UIButton+RuntimeAttributes.h"
#import "UIImageView+Coloring.h"
#import <SafariServices/SafariServices.h>
@implementation NSObject (Optimized)
+ (NSString *)className { return NSStringFromClass(self); }
- (void)performAfterDelay:(NSTimeInterval)delayInSec block:(MWMVoidBlock)block
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSec * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
block();
});
}
@end
@implementation UIView (Coordinates)
- (void)setMidX:(CGFloat)midX { self.center = CGPointMake(midX, self.center.y); }
- (CGFloat)midX { return self.center.x; }
- (void)setMidY:(CGFloat)midY { self.center = CGPointMake(self.center.x, midY); }
- (CGFloat)midY { return self.center.y; }
- (void)setOrigin:(CGPoint)origin
{
self.frame = CGRectMake(origin.x, origin.y, self.frame.size.width, self.frame.size.height);
}
- (CGPoint)origin { return self.frame.origin; }
- (void)setMinX:(CGFloat)minX
{
self.frame = CGRectMake(minX, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}
- (CGFloat)minX { return self.frame.origin.x; }
- (void)setMinY:(CGFloat)minY
{
self.frame = CGRectMake(self.frame.origin.x, minY, self.frame.size.width, self.frame.size.height);
}
- (CGFloat)minY { return self.frame.origin.y; }
- (void)setMaxX:(CGFloat)maxX
{
self.frame = CGRectMake(maxX - self.frame.size.width, self.frame.origin.y, self.frame.size.width,
self.frame.size.height);
}
- (CGFloat)maxX { return self.frame.origin.x + self.frame.size.width; }
- (void)setMaxY:(CGFloat)maxY
{
self.frame = CGRectMake(self.frame.origin.x, maxY - self.frame.size.height, self.frame.size.width,
self.frame.size.height);
}
- (CGFloat)maxY { return self.frame.origin.y + self.frame.size.height; }
- (void)setWidth:(CGFloat)width
{
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, width, self.frame.size.height);
}
- (CGFloat)width { return self.frame.size.width; }
- (void)setHeight:(CGFloat)height
{
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
}
- (CGFloat)height { return self.frame.size.height; }
- (CGSize)size { return self.frame.size; }
- (void)setSize:(CGSize)size
{
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, size.width, size.height);
}
- (void)sizeToIntegralFit
{
[self sizeToFit];
self.frame = CGRectIntegral(self.frame);
}
@end
@implementation UIApplication (URLs)
- (void)rateApp
{
NSString * urlString = @"https://apps.apple.com/app/comaps/id6747180809?action=write-review";
NSURL * url = [NSURL URLWithString:urlString];
[self openURL:url options:@{} completionHandler:nil];
}
@end
@implementation SolidTouchView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
@end
@implementation UIView (Refresh)
@end
@implementation UITableViewCell (Refresh)
@end
@implementation UINavigationBar (Refresh)
@end
@implementation UILabel (Refresh)
@end
@implementation UISlider (Refresh)
@end
@implementation UISwitch (Refresh)
@end
@implementation UIButton (Refresh)
@end
@implementation UITextView (Refresh)
@end
@implementation UIImageView (Refresh)
@end
@implementation SolidTouchImageView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
@end
@implementation UINavigationController (Autorotate)
- (BOOL)shouldAutorotate { return [self.viewControllers.lastObject shouldAutorotate]; }
@end
@implementation UIViewController (Autorotate)
- (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; }
@end
@interface UIViewController (SafariDelegateImpl)<SFSafariViewControllerDelegate>
@end
@implementation UIViewController (SafariDelegateImpl)
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller
{
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
@end
@implementation UIViewController (Safari)
- (BOOL)openUrl:(NSString * _Nonnull)urlString
{
return [self openUrl:urlString externally:NO];
}
- (BOOL)openUrl:(NSString *)urlString externally:(BOOL)externally
{
return [self openUrl:urlString externally:externally skipEncoding:NO];
}
- (BOOL)openUrl:(NSString *)urlString externally:(BOOL)externally skipEncoding:(BOOL)skipEncoding
{
NSString * encoded = urlString;
if (!skipEncoding && ![urlString canBeConvertedToEncoding:NSASCIIStringEncoding]) {
// TODO: This is a temporary workaround to open cyrillic/non-ASCII URLs.
// URLs in OSM are stored in UTF-8. NSURL constructor documentation says:
// > Must be a URL that conforms to RFC 2396. This method parses URLString according to RFCs 1738 and 1808.
// The right way to encode the URL string should be:
// 1. Split the (non-ASCII) string into components (host, path, query, fragment, etc.)
// 2. Encode each component separately (they have different allowed characters).
// 3. Merge them back into the string and create NSURL.
NSMutableCharacterSet * charset = [[NSMutableCharacterSet alloc] init];
[charset formUnionWithCharacterSet:NSCharacterSet.URLHostAllowedCharacterSet];
[charset formUnionWithCharacterSet:NSCharacterSet.URLPathAllowedCharacterSet];
[charset formUnionWithCharacterSet:NSCharacterSet.URLQueryAllowedCharacterSet];
[charset formUnionWithCharacterSet:NSCharacterSet.URLFragmentAllowedCharacterSet];
[charset addCharactersInString:@"#;/?:@&=+$,"];
encoded = [urlString stringByAddingPercentEncodingWithAllowedCharacters:charset];
}
// Matrix has an url with two hashes which doesn't work for NSURL and NSURLComponent.
NSRange const matrixUrl = [encoded rangeOfString:@"#/#"];
if (matrixUrl.location != NSNotFound)
encoded = [encoded stringByReplacingOccurrencesOfString:@"#/#" withString:@"#/%23"];
NSURLComponents * urlc = [NSURLComponents componentsWithString:encoded];
if (!urlc)
{
NSAssert(false, @"Invalid URL %@", urlString);
return NO;
}
// Some links in OSM do not have a scheme: www.some.link
if (!urlc.scheme)
urlc.scheme = @"http";
NSURL * url = urlc.URL;
if (externally && [UIApplication.sharedApplication canOpenURL:url])
{
[UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil];
}
else
{
SFSafariViewController * svc = [[SFSafariViewController alloc] initWithURL:url];
svc.delegate = self;
[self.navigationController presentViewController:svc animated:YES completion:nil];
}
return YES;
}
@end
@implementation UIImage (ImageWithColor)
+ (UIImage *)imageWithColor:(UIColor *)color
{
CGRect rect = CGRectMake(0.0, 0.0, 1.0, 1.0);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end

View file

@ -0,0 +1,8 @@
extension UILabel {
var numberOfVisibleLines: Int {
let textSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
let rowHeight = sizeThatFits(textSize).height.rounded()
let charHeight = font.pointSize.rounded()
return Int((rowHeight / charHeight).rounded())
}
}

View file

@ -0,0 +1,3 @@
@interface UILabel (RuntimeAttributes)
@property (copy, nonatomic) NSString * localizedText;
@end

View file

@ -0,0 +1,17 @@
#import "UILabel+RuntimeAttributes.h"
// Runtime attributes for setting localized text in Xib.
@implementation UILabel (RuntimeAttributes)
- (void)setLocalizedText:(NSString *)localizedText
{
self.text = L(localizedText);
}
- (NSString *)localizedText
{
NSString * text = self.text;
return L(text);
}
@end

View file

@ -0,0 +1,5 @@
extension UINib {
@objc convenience init(_ viewClass: AnyClass, bundle: Bundle? = nil) {
self.init(nibName: toString(viewClass), bundle: bundle)
}
}

View file

@ -0,0 +1,45 @@
extension UITableView {
@objc func registerNib(cellClass: AnyClass) {
register(UINib(cellClass), forCellReuseIdentifier: toString(cellClass))
}
@objc func registerClass(cellClass: AnyClass) {
register(cellClass, forCellReuseIdentifier: toString(cellClass))
}
@objc func dequeueReusableCell(withCellClass cellClass: AnyClass) -> UITableViewCell? {
return dequeueReusableCell(withIdentifier: toString(cellClass))
}
@objc func dequeueReusableCell(withCellClass cellClass: AnyClass, indexPath: IndexPath) -> UITableViewCell {
return dequeueReusableCell(withIdentifier: toString(cellClass), for: indexPath)
}
func registerNib<Cell>(cell: Cell.Type) where Cell: UITableViewCell {
register(UINib(cell), forCellReuseIdentifier: toString(cell))
}
func registerNibs<Cell>(_ cells: [Cell.Type]) where Cell: UITableViewCell {
cells.forEach { registerNib(cell: $0) }
}
func register<Cell>(cell: Cell.Type) where Cell: UITableViewCell {
register(cell, forCellReuseIdentifier: toString(cell))
}
func dequeueReusableCell<Cell>(cell: Cell.Type) -> Cell? where Cell: UITableViewCell {
return dequeueReusableCell(withIdentifier: toString(cell)) as? Cell
}
func dequeueReusableCell<Cell>(cell: Cell.Type, indexPath: IndexPath) -> Cell where Cell: UITableViewCell {
return dequeueReusableCell(withIdentifier: toString(cell), for: indexPath) as! Cell
}
func registerNibForHeaderFooterView<View>(_ view: View.Type) where View: UIView {
register(UINib(view), forHeaderFooterViewReuseIdentifier: toString(view))
}
func dequeueReusableHeaderFooterView<View>(_ view: View.Type) -> View where View: UIView {
return dequeueReusableHeaderFooterView(withIdentifier: toString(view)) as! View
}
}

View file

@ -0,0 +1,18 @@
extension UITableView {
typealias Updates = () -> Void
typealias Completion = () -> Void
@objc func update(_ updates: Updates) {
performBatchUpdates(updates, completion: nil)
}
@objc func update(_ updates: Updates, completion: @escaping Completion) {
performBatchUpdates(updates, completion: { _ in
completion()
})
}
@objc func refresh() {
update {}
}
}

View file

@ -0,0 +1,5 @@
@interface UITextField (RuntimeAttributes)
@property (copy, nonatomic) NSString * localizedPlaceholder;
@end

View file

@ -0,0 +1,16 @@
#import "UITextField+RuntimeAttributes.h"
@implementation UITextField (RuntimeAttributes)
- (void)setLocalizedPlaceholder:(NSString *)placeholder
{
self.placeholder = L(placeholder);
}
- (NSString *)localizedPlaceholder
{
NSString * placeholder = self.placeholder;
return L(placeholder);
}
@end

View file

@ -0,0 +1,11 @@
#import "MWMTextView.h"
@interface UITextView (UITextView_RuntimeAttributes)
@end
@interface MWMTextView (RuntimeAttributes)
@property (copy, nonatomic) NSString * localizedPlaceholder;
@end

View file

@ -0,0 +1,27 @@
#import "UITextView+RuntimeAttributes.h"
@implementation UITextView (RuntimeAttributes)
- (void)setLocalizedText:(NSString *)localizedText
{
self.text = L(localizedText);
}
- (NSString *)localizedText
{
return L(self.text);
}
@end
@implementation MWMTextView (RuntimeAttributes)
- (void)setLocalizedPlaceholder:(NSString *)localizedPlaceholder
{
self.placeholder = L(localizedPlaceholder);
}
- (NSString *)localizedPlaceholder
{
return L(self.placeholder);
}
@end

View file

@ -0,0 +1,30 @@
extension UIView {
enum SeparatorPosition {
case top
case bottom
}
@discardableResult
func addSeparator(_ position: SeparatorPosition = .top,
thickness: CGFloat = 1.0,
insets: UIEdgeInsets = .zero) -> UIView {
let lineView = UIView()
lineView.setStyleAndApply(.divider)
lineView.isUserInteractionEnabled = false
lineView.translatesAutoresizingMaskIntoConstraints = false
addSubview(lineView)
NSLayoutConstraint.activate([
lineView.heightAnchor.constraint(equalToConstant: thickness),
lineView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left),
lineView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.right),
])
switch position {
case .top:
lineView.topAnchor.constraint(equalTo: topAnchor, constant: insets.top).isActive = true
case .bottom:
lineView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -insets.bottom).isActive = true
}
return lineView
}
}

View file

@ -0,0 +1,40 @@
extension UIView {
@objc func animateConstraints(duration: TimeInterval,
animations: @escaping () -> Void,
completion: @escaping () -> Void) {
setNeedsLayout()
UIView.animate(withDuration: duration,
animations: { [weak self] in
animations()
self?.layoutIfNeeded()
},
completion: { _ in completion() })
}
@objc func animateConstraints(animations: @escaping () -> Void, completion: @escaping () -> Void) {
animateConstraints(duration: kDefaultAnimationDuration, animations: animations, completion: completion)
}
@objc func animateConstraints(animations: @escaping () -> Void) {
animateConstraints(duration: kDefaultAnimationDuration, animations: animations, completion: {})
}
@objc func startRotation(_ duration: TimeInterval = 1.0) {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = Double.pi * 2
rotationAnimation.duration = duration;
rotationAnimation.isCumulative = true;
rotationAnimation.repeatCount = Float.greatestFiniteMagnitude;
rotationAnimation.isRemovedOnCompletion = false
layer.add(rotationAnimation, forKey: "rotationAnimation")
}
@objc func stopRotation() {
layer.removeAnimation(forKey: "rotationAnimation")
}
@objc var isRotating: Bool {
return layer.animationKeys()?.contains("rotationAnimation") ?? false
}
}

View file

@ -0,0 +1,13 @@
extension UIView {
func center(inContainerView containerView: UIView) -> CGPoint {
guard let sv = superview else { return .zero }
var centerPoint = center
if let scrollView = sv as? UIScrollView, scrollView.zoomScale != 1.0 {
centerPoint.x += (scrollView.bounds.width - scrollView.contentSize.width) / 2.0 + scrollView.contentOffset.x
centerPoint.y += (scrollView.bounds.height - scrollView.contentSize.height) / 2.0 + scrollView.contentOffset.y
}
return sv.convert(centerPoint, to: containerView)
}
}

View file

@ -0,0 +1,20 @@
extension UIView {
@objc func hasSubview(viewClass: AnyClass) -> Bool {
return !subviews.filter { type(of: $0) == viewClass }.isEmpty
}
func clearTreeBackground() {
backgroundColor = UIColor.clear
subviews.forEach { $0.clearTreeBackground() }
}
func alignToSuperview(_ insets: UIEdgeInsets = .zero) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: superview!.topAnchor, constant: insets.top),
leftAnchor.constraint(equalTo: superview!.leftAnchor, constant: insets.left),
bottomAnchor.constraint(equalTo: superview!.bottomAnchor, constant: insets.bottom),
rightAnchor.constraint(equalTo: superview!.rightAnchor, constant: insets.right)
])
}
}

View file

@ -0,0 +1,25 @@
extension UIView {
@objc
func highlight() {
let color = UIColor.linkBlueHighlighted().withAlphaComponent(0.2)
let duration: TimeInterval = kDefaultAnimationDuration
let overlayView = UIView(frame: bounds)
overlayView.backgroundColor = color
overlayView.alpha = 0
overlayView.clipsToBounds = true
overlayView.isUserInteractionEnabled = false
overlayView.layer.cornerRadius = layer.cornerRadius
overlayView.layer.maskedCorners = layer.maskedCorners
addSubview(overlayView)
UIView.animate(withDuration: duration, delay: duration, options: .curveEaseInOut, animations: {
overlayView.alpha = 1
}) { _ in
UIView.animate(withDuration: duration, delay: duration * 3, options: .curveEaseInOut, animations: {
overlayView.alpha = 0
}) { _ in
overlayView.removeFromSuperview()
}
}
}
}

View file

@ -0,0 +1,22 @@
extension UIView {
@objc var snapshot: UIView {
guard let contents = layer.contents else {
return snapshotView(afterScreenUpdates: true)!
}
let snapshot: UIView
if let view = self as? UIImageView {
snapshot = UIImageView(image: view.image)
snapshot.bounds = view.bounds
} else {
snapshot = UIView(frame: frame)
snapshot.layer.contents = contents
snapshot.layer.bounds = layer.bounds
}
snapshot.layer.setCornerRadius(.custom(layer.cornerRadius))
snapshot.layer.masksToBounds = layer.masksToBounds
snapshot.contentMode = contentMode
snapshot.transform = transform
return snapshot
}
}

View file

@ -0,0 +1,12 @@
extension UIViewController {
@objc static func topViewController() -> UIViewController {
let window = UIApplication.shared.delegate!.window!!
if var topController = window.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
return (window.rootViewController as! UINavigationController).topViewController!
}
}

View file

@ -0,0 +1,8 @@
@interface UIViewController (Navigation)
- (void)goBack;
- (UIBarButtonItem *)buttonWithImage:(UIImage *)image action:(SEL)action;
- (NSArray<UIBarButtonItem *> *)alignedNavBarButtonItems:(NSArray<UIBarButtonItem *> *)items;
@end

View file

@ -0,0 +1,30 @@
#import "UIButton+Orientation.h"
#import "UIViewController+Navigation.h"
static CGFloat const kButtonExtraWidth = 16.0;
@implementation UIViewController (Navigation)
- (UIBarButtonItem *)negativeSpacer
{
UIBarButtonItem * spacer =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil
action:nil];
spacer.width = -kButtonExtraWidth;
return spacer;
}
- (UIBarButtonItem *)buttonWithImage:(UIImage *)image action:(SEL)action
{
return [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:action];
}
- (NSArray<UIBarButtonItem *> *)alignedNavBarButtonItems:(NSArray<UIBarButtonItem *> *)items
{
return [@[ [self negativeSpacer] ] arrayByAddingObjectsFromArray:items];
}
- (void)goBack { [self.navigationController popViewControllerAnimated:YES]; }
@end

View file

@ -0,0 +1,9 @@
extension UIViewController {
func alternativeSizeClass<T>(iPhone: @autoclosure () -> T, iPad: @autoclosure () -> T) -> T {
isiPad ? iPad() : iPhone()
}
func alternativeSizeClass(iPhone: () -> Void, iPad: () -> Void) {
isiPad ? iPad() : iPhone()
}
}

View file

@ -0,0 +1,8 @@
import Foundation
extension URL {
func queryParams() -> [String : String]? {
guard let urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil }
return urlComponents.queryItems?.reduce(into: [:], { $0[$1.name] = $1.value })
}
}