CompareShots v1.2
Вышло обновление CompareShots версии 1.2
Выпустил потому, что написал один из пользователей на почту просьбу скрывать логотип и текст после того, как выбрана картинка, т.к. иначе, если она не на полный экран, то они торчат из-за картинки или просвечивают. Попутно решил ещё прокачать приложение. Наконец-то пригодилась подписка на разные новые библиотеки. Поверх картинок теперь можно рисовать. Разными цветами и кистями разного размера. Один вечер - и такая красота :)
comment commentsКастомный бейдж для UITabBarController на Swift
Шёл 2016 год, а для того, чтобы изменить цвета для бейджа внутри таба всё ещё необходимо создавать свой UI-элемент и добавлять его в таббар вручную. Работающее решение на Swift:
// MARK: - UITabBarController extension for badges
extension UITabBarController {
/**
Set badges
- parameter badgeValues: values array
*/
func setBadges(badgeValues: [Int]) {
for view in self.tabBar.subviews {
if view is CustomTabBadge {
view.removeFromSuperview()
}
}
for index in 0...badgeValues.count-1 {
if badgeValues[index] != 0 {
addBadge(
index: index,
value: badgeValues[index],
color:UIColor.yellowColor(),
font: UIFont.systemFontOfSize(11)
)
}
}
}
/**
Add badge for tab
- parameter index: index of tab
- parameter value: badge value
- parameter color: badge color
- parameter font: badge font
*/
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let badgeView = CustomTabBadge()
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.whiteColor()
badgeView.textAlignment = .Center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = color
badgeView.tag = index
tabBar.addSubview(badgeView)
self.positionBadges()
}
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tabBar.setNeedsLayout()
self.tabBar.layoutIfNeeded()
self.positionBadges()
}
/**
Positioning
*/
func positionBadges() {
var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
return view.userInteractionEnabled // only UITabBarButton are userInteractionEnabled
}
tabbarButtons = tabbarButtons.sort({ $0.frame.origin.x < $1.frame.origin.x })
for view in self.tabBar.subviews {
if view is CustomTabBadge {
let badgeView = view as! CustomTabBadge
self.positionBadge(badgeView, items:tabbarButtons, index: badgeView.tag)
}
}
}
/**
Position for badge
- parameter badgeView: badge view
- parameter items: tab bar buttons array
- parameter index: index of tab
*/
func positionBadge(badgeView: UIView, items: [UIView], index: Int) {
let itemView = items[index]
let center = itemView.center
let xOffset: CGFloat = 12
let yOffset: CGFloat = -14
badgeView.frame.size = CGSizeMake(17, 17)
badgeView.center = CGPointMake(center.x + xOffset, center.y + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
tabBar.bringSubviewToFront(badgeView)
}
}
/// Custom UILabel class for badge view
class CustomTabBadge: UILabel {}
comment
comments
Функция задержки на Swift
Очень приятный синтаксический сахар - функция для запуска кода с задержкой через Grand Central Dispatch с помощью dispatch_after:
Update 2024. Swift 5.5+
Если нужна задержка внутри async-функции:func someMethod() async throws {
// sleep for 2 seconds
try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2)
}
Swift 2:
/**
Delay function using GCD. Syntax sugar.
- parameter delay: delay in seconds
- parameter closure: closure code to execute after delay
*/
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Swift 3:
/**
Delay function using GCD. Syntax sugar.
- parameter delay: delay in seconds
- parameter closure: closure code to execute after delay
*/
func delay(_ delay: Double, closure:()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
Использование:
delay(0.4) {
// код
}
comment
comments
Модальный UIVIewController с прозрачностью
Шпаргалка - как запрезентить UIViewController поверх другого так, чтобы он был полупрозрачный и под ним просвечивал контент.
Выставляем в Storyboard для Segue поле Kind как «Present Modally». Для UIViewController, который показываем, выставляем цвет фона Clear Color, делаем внутри фон (картинка или другой UIView) со значением alpha, например, 0.5.
В коде:
self.performSegueWithIdentifier("segueName", sender: self)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueName" {
let vc = segue.destinationViewController as! YourViewController
vc.modalPresentationStyle = .Custom
}
}
comment
comments
Редизайн блога
Решил поизучать, как выглядят сайты в Material Design от Google. Нашёл у них библиотеку Material Design Lite и запили его на сайт. Стало читабельнее, вроде, и из коробки адаптивный дизайн, можно читать с мобилок, да и на ретине выглядит теперь сочнее. Удобно, когда из готовых кусков можно собрать то, что тебе нравится. Результат мне нравится больше, чем то, что можно наваять, например, на Twitter Bootstrap.
Долго думал над шапкой и в итоге с любовью спёр идею у Apple с их страницы, посвящённой WWDC '16 :) запилил на SVG и анимировал курсор на JS (правда, только на десктопной версии). Смотрел на Polymer, но что-то оно как-то не завелось у меня сходу, да и уже на MDL многое сделал. Идея веб-компонентов, конечно, классная, и семантически круто выглядит, но пока отложу её до более хорошей поддержки браузерами. Как-то там, есть ощущение, что всё сильно на Node.JS завязано, а переписывать его на ноду мне уже не хочется, жду новых релизов Swift, чтобы на него переписать.
comment commentsТестирование асинхронного кода в XCTest
Пока писал тесты в приложении, столкнулся с тем, что не работают тесты, в которых есть асинхронный код. Например, если мы делаем асинхронный запрос в сеть и callback-блоке проверяем то, что получилось. Проверки просто не выболнялись и Xcode говорил, что тест удачно пройден, даже если он заведомо не должен пройти.
В ходе гугления оказалось два решения - одно через семафоры из Grand Central Dispatch, воторое с помощью expectations. С expectations мне понравилось больше. Пример теста:
import XCTest
class apiClientTests: XCTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
/**
SampleTest
*/
func testSampleAsyncRequest() {
let exp = expectationWithDescription("\(#function)\(#line)")
APIClient.sharedInstance.someMethod { (responseObject, error) in
XCTAssertNotNil(responseObject, "responseObject should not be nil")
XCTAssertNil(error, "error should be nil")
exp.fulfill()
}
waitForExpectationsWithTimeout(40) { (error) in
if error != nil {
XCTAssertTrue(false)
}
}
}
}
P.S. - если хочешь, чтобы тесты запускались в определённом порядке - то надо помнить, что они запускаются тупо в алфавитном порядке. То есть надо именовать тесты test1Name, test2Name и т.д.
comment comments