Compare commits

...

45 Commits

Author SHA1 Message Date
Ivan Vorobei 7f998787a4 Update to 1.2.1 version
Now if set custom height more default value, it aotu change to default value. Update example and pod version.
2019-01-18 13:51:49 +03:00
Ivan Vorobei 618da7d9ea Update README.md 2019-01-18 11:58:08 +03:00
Ivan Vorobei f062d07f94 Add Images 2019-01-15 20:31:03 +03:00
Ivan Vorobei 926e1f643c Add Images 2019-01-15 20:28:19 +03:00
Ivan Vorobei cdd8f4a2f4 Remove images 2019-01-13 14:28:54 +03:00
Ivan Vorobei 30cf10c595 Update README.md 2019-01-13 14:28:10 +03:00
Ivan Vorobei 884791b05c Create youtube3.jpg 2019-01-13 14:27:50 +03:00
Ivan Vorobei 4d0530e0a1 Update README.md 2019-01-13 14:25:42 +03:00
Ivan Vorobei 48741988b2 img 2019-01-13 14:25:20 +03:00
Ivan Vorobei 6297d5a5db Update youtube.jpg 2019-01-13 14:24:45 +03:00
Ivan Vorobei 3f60e48547 Update README.md 2019-01-13 14:23:44 +03:00
Ivan Vorobei 2ffe86afb9 Update youtube.jpg 2019-01-13 14:23:12 +03:00
Ivan Vorobei cf5bf12513 Update README.md 2019-01-13 14:18:03 +03:00
Ivan Vorobei 96aa1a6064 Create youtube.jpg 2019-01-13 14:15:38 +03:00
Ivan Vorobei 3241abd195 Delete SPStorkController-YouTube.jpg 2019-01-02 12:08:27 +03:00
Ivan Vorobei b72f10c7bd Update README.md 2019-01-02 12:08:06 +03:00
Ivan Vorobei 40bcff3cb6 Create SPStorkController-YouTube.jpg 2019-01-02 12:07:19 +03:00
Ivan Vorobei 01cc226253 Update README.md 2019-01-01 22:51:43 +03:00
Ivan Vorobei 691045d012 Update README.md 2019-01-01 22:49:49 +03:00
Ivan Vorobei 59f52f258a Update README.md 2018-12-31 18:50:40 +03:00
Ivan Vorobei bfece3194a Update README.md 2018-12-31 18:49:18 +03:00
Ivan Vorobei 6cccf24ffa Update README.md 2018-12-31 18:47:17 +03:00
Ivan Vorobei 635878e456 Update README.md 2018-12-30 18:55:52 +03:00
Ivan Vorobei d839e0f414 Update README.md 2018-12-30 18:54:01 +03:00
Ivan Vorobei e3cbc318be Update README.md 2018-12-30 18:51:38 +03:00
Ivan Vorobei e4232ec045 Update README.md 2018-12-29 17:31:29 +03:00
Ivan Vorobei 02346c0814 Update README.md 2018-12-28 21:55:13 +03:00
Ivan Vorobei 0b98593954 Update README.md 2018-12-28 21:54:36 +03:00
Ivan Vorobei 518700f9cd Delete spstork-preivew.jpg 2018-12-28 20:53:53 +03:00
Ivan Vorobei 5e02985ef2 Update README.md 2018-12-28 20:33:00 +03:00
Ivan Vorobei 539589e029 Update README.md 2018-12-28 19:50:04 +03:00
Ivan Vorobei 3434c487cd Update README.md 2018-12-28 19:48:53 +03:00
Ivan Vorobei c18e72add2 Update README.md 2018-12-28 19:46:59 +03:00
Ivan Vorobei c5e407fd80 Update README.md 2018-12-28 19:46:16 +03:00
Ivan Vorobei 3937c9caa5 Update README.md 2018-12-28 19:45:34 +03:00
Ivan Vorobei 38332bed39 Update README.md 2018-12-28 19:44:41 +03:00
Ivan Vorobei 40c7eee5de Update README.md 2018-12-28 19:43:48 +03:00
Ivan Vorobei 2e881179ee Update README.md 2018-12-28 19:42:10 +03:00
Ivan Vorobei 749640d359 Add video tutorial 2018-12-28 19:41:29 +03:00
Ivan Vorobei fd68fcb9f5 Update Readme 2018-12-28 18:44:07 +03:00
Ivan Vorobei 473f8ae0f1 Update README.md 2018-12-28 18:43:08 +03:00
Ivan Vorobei 75825b5e0a Update README 2018-12-28 18:42:17 +03:00
Ivan Vorobei f1e053d0d3 Update UserInterfaceState.xcuserstate 2018-12-24 16:49:58 +03:00
Ivan Vorobei 6208c93d40 Update README.md 2018-12-18 21:16:13 +03:00
Ivan Vorobei 8c64cd09da Update README.md 2018-12-18 16:32:23 +03:00
90 changed files with 1930 additions and 3334 deletions
File diff suppressed because it is too large Load Diff
@@ -65,7 +65,13 @@ class SPStorkPresentationController: UIPresentationController, UIGestureRecogniz
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else { return .zero }
let additionTranslate = containerView.bounds.height - (self.customHeight ?? containerView.bounds.height)
var customHeight = self.customHeight ?? containerView.bounds.height
if customHeight > containerView.bounds.height {
customHeight = containerView.bounds.height
print("SPStorkController - Custom height change to default value. Your height more maximum value")
}
let additionTranslate = containerView.bounds.height - customHeight
let yOffset: CGFloat = self.topSpace + 13 + additionTranslate
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
}
@@ -20,6 +20,7 @@
// SOFTWARE.
import UIKit
import StoreKit
struct SPApp {
@@ -56,5 +57,12 @@ struct SPApp {
}
}
static func set(elementsColor: UIColor) {
UINavigationController.elementsColor = elementsColor
UIAlertController.elementsColor = elementsColor
UITabBarController.elementsColor = elementsColor
UITabBar.appearance().tintColor = elementsColor
}
private init() {}
}
@@ -21,25 +21,24 @@
import UIKit
struct SPLaunch {
extension SPApp {
static func run() {
self.count += 1
}
static var count: Int {
get {
return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0
public struct Badge {
static var number: Int {
get {
return UIApplication.shared.applicationIconBadgeNumber
}
set {
UIApplication.shared.applicationIconBadgeNumber = newValue
}
}
set {
UserDefaults.standard.set(newValue, forKey: "SPLaunchCount")
static func reset() {
UIApplication.shared.applicationIconBadgeNumber = 0
}
private init() {}
}
static var isFirstOpen: Bool {
return (self.count == 1) || (self.count == 0)
}
private init() {}
}
@@ -21,22 +21,27 @@
import UIKit
class SPImageCollectionViewCell: SPCollectionContainerCell<SPDownloadingImageView> {
extension SPApp {
override init(frame: CGRect) {
super.init(frame: frame)
self.view.layer.cornerRadius = 10
self.view.contentMode = .scaleAspectFill
self.view.setNative()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
self.view.contentMode = .scaleAspectFill
self.view.startLoading()
struct Launch {
static func run() {
self.count += 1
}
static var count: Int {
get {
return UserDefaults.standard.value(forKey: "SPLaunchCount") as? Int ?? 0
}
set {
UserDefaults.standard.set(newValue, forKey: "SPLaunchCount")
}
}
static var isFirstLaunch: Bool {
return (self.count == 1) || (self.count == 0)
}
private init() {}
}
}
@@ -0,0 +1,86 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import SafariServices
extension SPApp {
static func open(app: SPSystemApp) {
switch app {
case SPSystemApp.photos:
guard let settingsUrl = URL(string: "photos-redirect://") else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("SPApp - Photos opened: \(success)")
})
} else {
UIApplication.shared.openURL(settingsUrl as URL)
}
} else {
print("SPApp - Photos not opened")
}
case SPSystemApp.setting:
DispatchQueue.main.async {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("SPApp - Settings opened: \(success)")
})
} else {
UIApplication.shared.openURL(settingsUrl as URL)
}
} else {
print("SPApp - Settings not opened")
}
}
}
}
static func open(link: String, redirect: Bool) {
guard let url = URL(string: link) else {
print("SPOpener - can not create URL")
return
}
if redirect {
UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
if let rootController = SPApp.rootController {
let safariController = SFSafariViewController.init(url: url)
rootController.present(safariController, animated: true, completion: nil)
}
}
}
}
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -56,6 +56,12 @@ struct SPAppStore {
}
}
static func requestReview() {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
}
static func isUpdateAvailable(completion: @escaping (Bool)->()) {
guard let info = Bundle.main.infoDictionary,
@@ -128,7 +134,6 @@ extension String {
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -36,7 +36,6 @@ public struct SPAudio {
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String {
return input.rawValue
}
@@ -21,4 +21,7 @@
import UIKit
struct SPCodeDraw { private init(){} }
struct SPCodeDraw {
private init() {}
}
@@ -19,7 +19,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
extension SPCodeDraw {
@@ -23,15 +23,19 @@ import UIKit
public struct SPConstraints {
static func setEqualSize(_ view: UIView, superVuew: UIView) {
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: superVuew.topAnchor),
view.leftAnchor.constraint(equalTo: superVuew.leftAnchor),
view.rightAnchor.constraint(equalTo: superVuew.rightAnchor),
view.bottomAnchor.constraint(equalTo: superVuew.bottomAnchor)
])
static func setEqualSizeSuperview(for view: UIView) {
if let superView = view.superview {
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: superView.topAnchor),
view.leftAnchor.constraint(equalTo: superView.leftAnchor),
view.rightAnchor.constraint(equalTo: superView.rightAnchor),
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor)
])
}
}
private init() {}
}
@@ -23,11 +23,11 @@ import UIKit
struct SPDevice {
static var isIphone: Bool {
static var iphone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
static var isIpad: Bool {
static var ipad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
@@ -19,22 +19,20 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import Foundation
public struct SPBadge {
extension Date {
static func reset() {
UIApplication.shared.applicationIconBadgeNumber = 0
func format(mask: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = mask
return dateFormatter.string(from: self)
}
static var number: Int {
get {
return UIApplication.shared.applicationIconBadgeNumber
}
set {
UIApplication.shared.applicationIconBadgeNumber = newValue
}
static func create(from value: String) -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
let date = formatter.date(from: value)
return date
}
private init() {}
}
@@ -24,6 +24,10 @@ import UIKit
extension String {
var digits: String {
return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
mutating func dropLast(substring: String) {
if self.hasSuffix(substring) {
self = String(dropLast(substring.count))
@@ -69,4 +73,8 @@ extension String {
mutating func replace(_ replacingString: String, with newString: String) {
self = self.replacingOccurrences(of: replacingString, with: newString)
}
func replace(_ replacingString: String, with newString: String) -> String {
return self.replacingOccurrences(of: replacingString, with: newString)
}
}
@@ -98,11 +98,11 @@ extension UIButton {
}
func setAnimatableText(_ text: String, withComplection completion: (() -> Void)! = {}) {
SPAnimation.animate(0.2, animations: {
SPAnimation.animate(0.3, animations: {
self.titleLabel?.alpha = 0
}, withComplection: {
self.setTitle(text, for: .normal)
SPAnimation.animate(0.2, animations: {
SPAnimation.animate(0.3, animations: {
self.titleLabel?.alpha = 1
}, withComplection: {
completion()
@@ -23,55 +23,10 @@ import UIKit
public extension UIFont {
public struct fonts {
public static func AvenirNext(type: BoldType, size: CGFloat) -> UIFont {
return UIFont.createFont(.AvenirNext, boldType: type, size: size)
}
}
public static func system(type: BoldType, size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) {
return UIFont.systemFont(ofSize: size, weight: self.getBoldTypeBy(boldType: type))
} else {
return self.createFont(.AvenirNext, boldType: type, size: size)
}
return UIFont.systemFont(ofSize: size, weight: self.getBoldTypeBy(boldType: type))
}
public static func createFont(_ fontType: FontType, boldType: BoldType, size: CGFloat) -> UIFont {
return UIFont.init(
name: self.getFontNameBy(fontType: fontType) + self.getBoldTypeNameBy(boldType: boldType),
size: size
)!
}
private static func getFontNameBy(fontType: FontType) -> String {
switch fontType {
case .AvenirNext:
return "AvenirNext"
}
}
private static func getBoldTypeNameBy(boldType: BoldType) -> String {
switch boldType {
case .UltraLight:
return "-UltraLight"
case .Light:
return "-Light"
case .Medium:
return "-Medium"
case .Regular:
return "-Regular"
case .Bold:
return "-Bold"
case .DemiBold:
return "-DemiBold"
default:
return "-Regular"
}
}
@available(iOS 8.2, *)
private static func getBoldTypeBy(boldType: BoldType) -> UIFont.Weight {
switch boldType {
@@ -94,10 +49,6 @@ public extension UIFont {
}
}
public enum FontType {
case AvenirNext
}
public enum BoldType {
case Regular
case Medium
@@ -25,7 +25,7 @@ extension UIImageView {
public func setNative() {
self.layer.borderWidth = 0.5
self.layer.borderColor = SPNativeStyleKit.Colors.midGray.cgColor
self.layer.borderColor = SPNativeColors.midGray.cgColor
self.layer.masksToBounds = true
}
@@ -53,4 +53,25 @@ public extension UILabel {
attributedText = NSAttributedString(string: textString, attributes: attrs)
}
}
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
@@ -28,7 +28,7 @@ extension UINavigationController {
if UINavigationBar.appearance().tintColor != nil {
return UINavigationBar.appearance().tintColor
} else {
return SPNativeStyleKit.Colors.blue
return SPNativeColors.blue
}
}
set {
@@ -23,6 +23,19 @@ import UIKit
extension UITabBarController {
static var elementsColor: UIColor {
get {
if UITabBar.appearance().tintColor != nil {
return UITabBar.appearance().tintColor
} else {
return SPNativeColors.blue
}
}
set {
UINavigationBar.appearance().tintColor = newValue
}
}
func addTabBarItem(title: String, image: UIImage, selectedImage: UIImage? = nil, controller: UIViewController) {
let tabBarItem = UITabBarItem(
@@ -33,7 +33,7 @@ extension UITableViewCell {
}
set {
let backgroundView = UIView()
backgroundView.backgroundColor = SPNativeStyleKit.Colors.customGray
backgroundView.backgroundColor = SPNativeColors.customGray
self.selectedBackgroundView = backgroundView
}
}
@@ -32,7 +32,7 @@ extension UIViewController {
self.dismiss(animated: true, completion: nil)
}
func wrapToNavigationController(statusBar: SPStatusBar = .dark) -> UINavigationController {
public func wrapToNavigationController(statusBar: SPStatusBar = .dark) -> UINavigationController {
let controller = SPStatusBarManagerNavigationController(rootViewController: self)
controller.statusBar = statusBar
return controller
@@ -119,7 +119,7 @@ extension UIViewController {
}
}
func setNavigationTitle(_ title: String, style: SPNavigationTitleStyle) {
public func setNavigationTitle(_ title: String, style: SPNavigationTitleStyle) {
self.navigationItem.title = title
switch style {
case .large:
@@ -34,20 +34,17 @@ public extension UIView {
public extension UIView {
var topSafeArea: CGFloat {
var topSafeArea: CGFloat = 0
var safeArea: UIEdgeInsets {
if #available(iOS 11.0, *) {
topSafeArea = self.safeAreaInsets.top
return self.safeAreaInsets
} else{
return UIEdgeInsets.zero
}
return topSafeArea
}
var bottomSafeArea: CGFloat {
var bottomSafeArea: CGFloat = 0
if #available(iOS 11.0, *) {
bottomSafeArea = self.safeAreaInsets.bottom
}
return bottomSafeArea
func set(width: CGFloat, height: CGFloat) {
self.setHeight(height)
self.setWidth(width)
}
func setHeight(_ height: CGFloat) {
@@ -82,10 +79,10 @@ public extension UIView {
if self.superview == nil { return }
self.frame = CGRect.init(origin: CGPoint.zero, size: self.superview!.frame.size)
if customWidth != nil {
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.frame.height))
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: customWidth!, height: self.superview!.frame.height))
}
if customHeight != nil {
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.frame.width, height: customHeight!))
self.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.superview!.frame.width, height: customHeight!))
}
}
@@ -153,7 +150,7 @@ public extension UIView {
let gradeView = UIView.init()
gradeView.alpha = 0
self.addSubview(gradeView)
SPConstraints.setEqualSize(gradeView, superVuew: self)
SPConstraints.setEqualSizeSuperview(for: gradeView)
gradeView.alpha = alpha
gradeView.backgroundColor = color
return gradeView
@@ -23,20 +23,30 @@ import UIKit
struct SPLayout {
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat, heightFactor: CGFloat, maxHeight: CGFloat, relativeSideFactor: CGFloat, from relativeSize: CGSize) -> CGSize {
static func sizeWith(widthFactor: CGFloat, maxWidth: CGFloat?, heightFactor: CGFloat, maxHeight: CGFloat?, relativeSideFactor: CGFloat?, from size: CGSize) -> CGSize {
var widthArea = relativeSize.width * widthFactor
var heightArea = relativeSize.height * heightFactor
var widthArea = size.width * widthFactor
var heightArea = size.height * heightFactor
widthArea.setIfMore(when: maxWidth)
heightArea.setIfMore(when: maxHeight)
if let maxWidth = maxWidth {
widthArea.setIfMore(when: maxWidth)
}
if let maxHeight = maxHeight {
heightArea.setIfMore(when: maxHeight)
}
var prepareWidth = widthArea
var prepareHeight = widthArea / relativeSideFactor
if prepareHeight > heightArea {
prepareHeight = heightArea
prepareWidth = heightArea * relativeSideFactor
var prepareHeight = heightArea
if let relativeSideFactor = relativeSideFactor {
prepareHeight = widthArea / relativeSideFactor
if prepareHeight > heightArea {
prepareHeight = heightArea
prepareWidth = heightArea * relativeSideFactor
}
}
return CGSize.init(width: prepareWidth, height: prepareHeight)
}
@@ -28,7 +28,7 @@ struct SPMail {
return MFMailComposeViewController.canSendMail()
}
static func openMailApp(to email: String, subject: String? = nil, body: String? = nil) {
static func openApp(to email: String, subject: String? = nil, body: String? = nil) {
let parametrs = "mailto:\(email)?subject=\(subject ?? "")&body=\(body ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
if parametrs != nil {
@@ -40,7 +40,7 @@ struct SPMail {
}
}
static func mailDialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
static func dialog(to email: String, subject: String? = nil, body: String? = nil, on viewController: UIViewController) {
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = SPMailSingltone.sharedInstance
@@ -69,7 +69,6 @@ struct SPMail {
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -0,0 +1,41 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
enum SPNativeColors {
static let red = UIColor.init(hex: "FF3B30")
static let orange = UIColor.init(hex: "FF9500")
static let yellow = UIColor.init(hex: "FFCC00")
static let green = UIColor.init(hex: "4CD964")
static let tealBlue = UIColor.init(hex: "5AC8FA")
static let blue = UIColor.init(hex: "007AFF")
static let purple = UIColor.init(hex: "5856D6")
static let pink = UIColor.init(hex: "FF2D55")
static let white = UIColor.init(hex: "FFFFFF")
static let customGray = UIColor.init(hex: "EFEFF4")
static let lightGray = UIColor.init(hex: "E5E5EA")
static let lightGray2 = UIColor.init(hex: "D1D1D6")
static let midGray = UIColor.init(hex: "C7C7CC")
static let gray = UIColor.init(hex: "8E8E93")
static let black = UIColor.init(hex: "000000")
}
@@ -29,7 +29,7 @@ struct SPLocalNotification {
let content = UNMutableNotificationContent()
content.body = body
content.title = title ?? ""
content.badge = NSNumber(value: SPBadge.number + 1)
content.badge = NSNumber(value: 1)
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
@@ -55,7 +55,7 @@ struct SPLocalNotification {
let content = UNMutableNotificationContent()
content.body = body
content.title = title ?? ""
content.badge = NSNumber(value: SPBadge.number + 1)
content.badge = NSNumber(value: 1)
content.sound = UNNotificationSound.default
let triggerDate = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: date)
@@ -1,111 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import SafariServices
struct SPOpener {
struct App {
static func system(app: SPSystemApp) {
switch app {
case SPSystemApp.photos:
guard let settingsUrl = URL(string: "photos-redirect://") else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("SPOpener - Photos opened: \(success)")
})
} else {
UIApplication.shared.openURL(settingsUrl as URL)
}
} else {
print("SPOpener - Photos not opened")
}
case SPSystemApp.setting:
DispatchQueue.main.async {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("SPOpener - Settings opened: \(success)")
})
} else {
UIApplication.shared.openURL(settingsUrl as URL)
}
} else {
print("SPOpener - Settings not opened")
}
}
}
}
private init() {}
}
struct Link {
public static func redirectToBrowserAndOpen(link: String) {
guard let url = URL(string: link) else {
print("SPOpener - can not create URL")
return
}
self.redirectToBrowserAndOpen(link: url)
}
public static func redirectToBrowserAndOpen(link: URL) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(link, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
UIApplication.shared.openURL(link)
}
}
public static func openInsideApp(link: String, on viewController: UIViewController) {
if let url = URL.init(string: link) {
let safariController = SFSafariViewController.init(url: url)
viewController.present(safariController, animated: true, completion: nil)
} else {
print("SPOpener - openInsideApp - invalid link")
}
}
private init() {}
}
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -74,7 +74,7 @@ public extension CGFloat {
}
public extension Collection {
/// Return a copy of `self` with its elements shuffled
func shuffle() -> [Iterator.Element] {
var list = Array(self)
list.shuffleInPlace()
@@ -90,9 +90,9 @@ extension Collection where Index == Int {
}
public extension MutableCollection where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffleInPlace() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in startIndex ..< endIndex - 1 {
@@ -24,11 +24,7 @@ import UIKit
class SPInstagram {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!) {
return true
} else {
return false
}
return UIApplication.shared.canOpenURL(URL(string: "instagram://user?username=test")!)
}
static func openPost(id: String) {
@@ -38,7 +34,7 @@ class SPInstagram {
if UIApplication.shared.canOpenURL(instagramUrl!) {
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
SPApp.open(link: safariURL.absoluteString, redirect: true)
}
}
@@ -49,14 +45,13 @@ class SPInstagram {
if UIApplication.shared.canOpenURL(instagramUrl!) {
UIApplication.shared.open(instagramUrl!, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
SPOpener.Link.redirectToBrowserAndOpen(link: safariURL)
SPApp.open(link: safariURL.absoluteString, redirect: true)
}
}
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -24,11 +24,7 @@ import UIKit
class SPTelegram {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!) {
return true
} else {
return false
}
return UIApplication.shared.canOpenURL(URL(string: "tg://msg?text=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
@@ -47,7 +43,7 @@ class SPTelegram {
static func joinChannel(id: String) {
let url = "https://t.me/joinchat/\(id)"
SPOpener.Link.redirectToBrowserAndOpen(link: url)
SPApp.open(link: url, redirect: true)
}
static func openBot(username: String) {
@@ -56,13 +52,12 @@ class SPTelegram {
username.removeFirst()
}
let url = "https://telegram.me/\(username)"
SPOpener.Link.redirectToBrowserAndOpen(link: url)
SPApp.open(link: url, redirect: true)
}
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -24,11 +24,7 @@ import UIKit
class SPTwitter {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!) {
return true
} else {
return false
}
return UIApplication.shared.canOpenURL(URL(string: "twitter://post?message=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
@@ -48,7 +44,6 @@ class SPTwitter {
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -24,11 +24,7 @@ import UIKit
class SPViber {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "viber://forward?text=test")!) {
return true
} else {
return false
}
return UIApplication.shared.canOpenURL(URL(string: "viber://forward?text=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
@@ -48,9 +44,6 @@ class SPViber {
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -24,11 +24,7 @@ import UIKit
class SPWhatsApp {
static var isSetApp: Bool {
if UIApplication.shared.canOpenURL(URL(string: "whatsapp://send?text=test")!) {
return true
} else {
return false
}
return UIApplication.shared.canOpenURL(URL(string: "whatsapp://send?text=test")!)
}
static func share(text: String, complection: @escaping (_ isOpened: Bool)->() = {_ in }) {
@@ -48,7 +44,6 @@ class SPWhatsApp {
private init() {}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
@@ -1,48 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public struct SPNativeStyleKit {
struct Colors {
static let red = UIColor.init(hex: "FF3B30")
static let orange = UIColor.init(hex: "FF9500")
static let yellow = UIColor.init(hex: "FFCC00")
static let green = UIColor.init(hex: "4CD964")
static let tealBlue = UIColor.init(hex: "5AC8FA")
static let blue = UIColor.init(hex: "007AFF")
static let purple = UIColor.init(hex: "5856D6")
static let pink = UIColor.init(hex: "FF2D55")
static let white = UIColor.init(hex: "FFFFFF")
static let customGray = UIColor.init(hex: "EFEFF4")
static let lightGray = UIColor.init(hex: "E5E5EA")
static let lightGray2 = UIColor.init(hex: "D1D1D6")
static let midGray = UIColor.init(hex: "C7C7CC")
static let gray = UIColor.init(hex: "8E8E93")
static let black = UIColor.init(hex: "000000")
private init() {}
}
private init() {}
}
@@ -26,11 +26,6 @@ public enum SPStatusBar {
case light
}
public enum SPSnapToSide {
case left
case right
}
public enum SPSystemIconType {
case share
case close
@@ -73,3 +68,8 @@ public enum SPSystemApp {
case photos
case setting
}
public enum SPSelectionType {
case select
case unselect
}
@@ -23,21 +23,21 @@ import UIKit
class SPAppleMusicButton: SPButton {
var mode: Mode = .unselect {
var type: SPSelectionType = .unselect {
didSet {
self.updateStyle(animated: false)
self.updateType(animated: false)
}
}
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
didSet {
self.updateStyle(animated: false)
self.updateType(animated: false)
}
}
var baseColor: UIColor = UIColor.init(hex: "F8F7FC") {
didSet {
self.updateStyle(animated: false)
self.updateType(animated: false)
}
}
@@ -46,11 +46,11 @@ class SPAppleMusicButton: SPButton {
self.layer.cornerRadius = 8
self.titleLabel?.font = UIFont.system(type: .DemiBold, size: 15)
self.contentEdgeInsets = UIEdgeInsets.init(top: 12, left: 27, bottom: 12, right: 27)
self.mode = .unselect
self.type = .unselect
}
private func updateStyle(animated: Bool) {
switch self.mode {
private func updateType(animated: Bool) {
switch self.type {
case .select:
self.backgroundColor = self.selectColor
self.setTitleColor(UIColor.white)
@@ -61,10 +61,5 @@ class SPAppleMusicButton: SPButton {
break
}
}
enum Mode {
case select
case unselect
}
}
@@ -0,0 +1,93 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPAppleMusicSectionButtonsView: SPView {
let topSeparatorView = SPSeparatorView()
let bottomSeparatorView = SPSeparatorView()
let leftButton = SPAppleMusicButton()
let rightButton = SPAppleMusicButton()
var sectionHeight: CGFloat = 92 {
didSet { self.layoutSubviews() }
}
var buttonsSpace: CGFloat = 18 {
didSet { self.layoutSubviews() }
}
var selectColor: UIColor = UIColor.init(hex: "FD2D55") {
didSet {
self.leftButton.selectColor = selectColor
self.rightButton.selectColor = selectColor
}
}
var baseColor: UIColor = UIColor.init(hex: "F8F7FC") {
didSet {
self.leftButton.baseColor = baseColor
self.rightButton.baseColor = baseColor
}
}
override func commonInit() {
super.commonInit()
self.addSubview(self.topSeparatorView)
self.addSubview(self.bottomSeparatorView)
for button in [self.leftButton, self.rightButton] {
button.type = .unselect
button.setTitle("Title")
self.addSubview(button)
}
}
func layout(origin: CGPoint, width: CGFloat) {
self.frame.origin = origin
self.set(width: width, height: sectionHeight)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.topSeparatorView.frame.origin = .zero
self.topSeparatorView.setWidth(self.frame.width)
self.bottomSeparatorView.frame.origin.x = 0
self.bottomSeparatorView.frame.bottomYPosition = self.frame.height
self.bottomSeparatorView.setWidth(self.frame.width)
let buttonWidth = (self.frame.width - self.buttonsSpace) / 2
self.leftButton.sizeToFit()
self.leftButton.setWidth(buttonWidth)
self.leftButton.frame.origin.x = 0
self.leftButton.center.y = self.frame.height / 2
self.rightButton.sizeToFit()
self.rightButton.setWidth(buttonWidth)
self.rightButton.frame.bottomXPosition = self.frame.width
self.rightButton.center.y = self.frame.height / 2
}
}
@@ -59,7 +59,7 @@ class SPDotButton: SPButton {
override func commonInit() {
super.commonInit()
self.backgroundColor = UIColor.black.withAlphaComponent(0.2)
self.backgroundColor = UIColor.black.withAlphaComponent(0.5)
for _ in 0...2 {
let dotView = UIView()
dotView.isUserInteractionEnabled = false
@@ -46,7 +46,7 @@ class SPNativeLargeButton: SPDownloadingButton {
super.commonInit()
self.titleLabel?.font = UIFont.system(type: UIFont.BoldType.DemiBold, size: 16)
self.setTitleColor(UIColor.white)
self.backgroundColor = SPNativeStyleKit.Colors.blue
self.backgroundColor = SPNativeColors.blue
self.layer.masksToBounds = true
self.layer.cornerRadius = 8
self.contentEdgeInsets = UIEdgeInsets.init(top: 15, left: 15, bottom: 15, right: 15)
@@ -39,7 +39,7 @@ class SPPlayCircleButton: UIButton {
}
}
var iconColor = SPNativeStyleKit.Colors.white {
var iconColor = SPNativeColors.white {
didSet {
self.iconView.color = self.iconColor
}
@@ -52,7 +52,7 @@ class SPPlayCircleButton: UIButton {
self.addSubview(self.iconView)
self.iconView.isUserInteractionEnabled = false
self.setTitle("", for: .normal)
self.backgroundColor = SPNativeStyleKit.Colors.blue
self.backgroundColor = SPNativeColors.blue
self.audioState = .play
}
@@ -75,8 +75,8 @@ class SPSocialButton: UIButton {
fileprivate func commonInit() {
self.iconView.isUserInteractionEnabled = false
self.addSubview(self.iconView)
self.backgroundColor = SPNativeStyleKit.Colors.blue
self.iconView.color = SPNativeStyleKit.Colors.white
self.backgroundColor = SPNativeColors.blue
self.iconView.color = SPNativeColors.white
}
override func layoutSubviews() {
@@ -33,7 +33,7 @@ class SPSystemIconButton: UIButton {
}
}
var color = SPNativeStyleKit.Colors.blue {
var color = SPNativeColors.blue {
didSet {
self.iconView.color = self.color
}
@@ -1,162 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPNativeLoginCodeViewController: SPNativeTableViewController {
weak var delegate: SPLoginCodeControllerDelegate?
var content: SPNativeLoginNavigationController.LoginCodeContent!
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self.navigationController as! SPNativeLoginNavigationController
self.content = self.delegate?.loginCodeContent
self.setPrefersLargeNavigationTitle(self.content.navigationTitle)
self.dismissKeyboardWhenTappedAround()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 0)) as? SPFormTextFiledTableViewCell {
cell.textField.becomeFirstResponder()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 1
case 1:
return 1
default:
return 0
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var labelWidth: CGFloat = 0
for text in [self.content.codeTitle] {
let font = UIFont.system(type: .Regular, size: 17)
let fontAttributes = [NSAttributedString.Key.font: font]
let calculatedSize = NSString.init(string: text).size(withAttributes: fontAttributes)
labelWidth.setIfFewer(when: calculatedSize.width + 1)
}
switch indexPath {
case IndexPath.init(row: 0, section: 0):
let cell = self.dequeueTextFiledTableViewCell(indexPath: indexPath)
cell.label.text = self.content.codeTitle
cell.textField.placeholder = self.content.codePlaceholder
cell.textField.keyboardType = self.content.codeKeyboardType
cell.textField.delegate = self
cell.textField.returnKeyType = .done
cell.textField.autocapitalizationType = .none
cell.textField.autocorrectionType = .no
cell.textField.isSecureTextEntry = true
cell.fixWidthLabel = labelWidth
cell.separatorInsetStyle = .all
cell.textAligmentToSide = false
return cell
case IndexPath.init(row: 0, section: 1):
let cell = self.dequeueButtonTableViewCell(indexPath: indexPath)
cell.button.setTitle(self.content.buttonTitle, for: .normal)
cell.separatorInset.left = 0
cell.button.addTarget(self, action: #selector(self.enterAction), for: .touchUpInside)
return cell
default:
return UITableViewCell()
}
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
switch section {
case 0:
return self.content.commentTitle
default:
return nil
}
}
@objc func enterAction() {
var сodeCell: SPFormTextFiledTableViewCell? = nil
var buttonCell: SPFormButtonTableViewCell? = nil
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 0)) as? SPFormTextFiledTableViewCell {
сodeCell = cell
}
if сodeCell?.textField.isEmptyText ?? false {
сodeCell?.highlight()
return
}
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 1)) as? SPFormButtonTableViewCell {
buttonCell = cell
}
buttonCell?.button.startLoading()
сodeCell?.textField.isEnabled = false
self.delegate?.login(with: сodeCell?.textField.text ?? "") { (oAuthState) in
buttonCell?.button.stopLoading()
if oAuthState != SPOauthState.succsess {
UIAlertController.show(
title: self.content.errorOauthTitle,
message: self.content.errorOauthSubtitle,
buttonTitle: self.content.errorOauthButtonTitle,
on: self
)
сodeCell?.textField.isEnabled = true
сodeCell?.textField.becomeFirstResponder()
}
}
}
}
extension SPNativeLoginCodeViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
self.enterAction()
return true
}
}
protocol SPLoginCodeControllerDelegate: class {
var loginCodeContent: SPNativeLoginNavigationController.LoginCodeContent {get}
func login(with code: String, complection: @escaping (SPOauthState)->())
}
@@ -1,86 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPNativeLoginNavigationController: UINavigationController, SPLoginControllerDelegate, SPLoginCodeControllerDelegate {
let loginViewController = SPNativeLoginViewController()
let codeViewController = SPNativeLoginCodeViewController()
init() {
super.init(rootViewController: self.loginViewController)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
var loginContent: SPNativeLoginNavigationController.LoginContent {
return LoginContent()
}
var loginCodeContent: SPNativeLoginNavigationController.LoginCodeContent {
return LoginCodeContent()
}
struct LoginContent {
var navigationTitle: String = "Sign In"
var loginTitle: String = "Login"
var loginPlaceholder: String = "example@icloud.com"
var loginKeyboardType: UIKeyboardType = .emailAddress
var passwordTitle: String = "Password"
var passwordPlaceholder: String = "Required"
var commentTitle: String = "Please enter a pair of login and password"
var buttonTitle: String = "Sign In"
var errorOauthTitle: String = "Error"
var errorOauthSubtitle: String = "Invalid login or password"
var errorOauthButtonTitle: String = "Ok"
}
struct LoginCodeContent {
var navigationTitle: String = "Auth Code"
var codeTitle: String = "Code"
var codePlaceholder: String = "Required"
var codeKeyboardType: UIKeyboardType = .numberPad
var commentTitle: String = "Please enter a code for authentication"
var buttonTitle: String = "Sign In"
var errorOauthTitle: String = "Error"
var errorOauthSubtitle: String = "Invalid data"
var errorOauthButtonTitle: String = "Ok"
}
func login(with login: String, password: String, complection: @escaping (SPOauthState) -> ()) {
fatalError("SPLoginNavigationController - Need override func")
}
func login(with code: String, complection: @escaping (SPOauthState) -> ()) {
fatalError("SPLoginNavigationController - Need override func")
}
func needRequestCode() {
self.pushViewController(self.codeViewController, animated: true)
}
}
@@ -1,202 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPNativeLoginViewController: SPNativeTableViewController {
weak var delegate: SPLoginControllerDelegate?
var content: SPNativeLoginNavigationController.LoginContent!
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self.navigationController as! SPNativeLoginNavigationController
self.content = self.delegate?.loginContent
self.setPrefersLargeNavigationTitle(self.content.navigationTitle)
self.dismissKeyboardWhenTappedAround()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 0)) as? SPFormTextFiledTableViewCell {
cell.textField.becomeFirstResponder()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 2
case 1:
return 1
default:
return 0
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var labelWidth: CGFloat = 0
for text in [self.content.loginTitle, self.content.passwordTitle] {
let font = UIFont.system(type: .Regular, size: 17)
let fontAttributes = [NSAttributedString.Key.font: font]
let calculatedSize = NSString.init(string: text).size(withAttributes: fontAttributes)
labelWidth.setIfFewer(when: calculatedSize.width + 1)
}
switch indexPath {
case IndexPath.init(row: 0, section: 0):
let cell = self.dequeueTextFiledTableViewCell(indexPath: indexPath)
cell.label.text = self.content.loginTitle
cell.textField.placeholder = self.content.loginPlaceholder
cell.textField.keyboardType = self.content.loginKeyboardType
cell.textField.delegate = self
cell.textField.returnKeyType = .next
cell.textField.autocapitalizationType = .none
cell.textField.autocorrectionType = .no
cell.fixWidthLabel = labelWidth
cell.textAligmentToSide = false
return cell
case IndexPath.init(row: 1, section: 0):
let cell = self.dequeueTextFiledTableViewCell(indexPath: indexPath)
cell.label.text = self.content.passwordTitle
cell.textField.placeholder = self.content.passwordPlaceholder
cell.textField.isSecureTextEntry = true
cell.textField.delegate = self
cell.textField.returnKeyType = .done
cell.textField.autocapitalizationType = .none
cell.textField.autocorrectionType = .no
cell.separatorInset.left = 0
cell.fixWidthLabel = labelWidth
cell.separatorInsetStyle = .all
cell.textAligmentToSide = false
return cell
case IndexPath.init(row: 0, section: 1):
let cell = self.dequeueButtonTableViewCell(indexPath: indexPath)
cell.button.setTitle(self.content.buttonTitle, for: .normal)
cell.separatorInset.left = 0
cell.button.addTarget(self, action: #selector(self.enterAction), for: .touchUpInside)
return cell
default:
return UITableViewCell()
}
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
switch section {
case 0:
return self.content.commentTitle
default:
return nil
}
}
@objc func enterAction() {
var loginCell: SPFormTextFiledTableViewCell? = nil
var passwordCell: SPFormTextFiledTableViewCell? = nil
var buttonCell: SPFormButtonTableViewCell? = nil
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 0)) as? SPFormTextFiledTableViewCell {
loginCell = cell
}
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 1, section: 0)) as? SPFormTextFiledTableViewCell {
passwordCell = cell
}
if let cell = self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 1)) as? SPFormButtonTableViewCell {
buttonCell = cell
}
if loginCell?.textField.isEmptyText ?? false {
loginCell?.highlight()
return
}
if passwordCell?.textField.isEmptyText ?? false {
passwordCell?.highlight()
return
}
buttonCell?.button.startLoading()
loginCell?.textField.isEnabled = false
passwordCell?.textField.isEnabled = false
self.delegate?.login(with: loginCell?.textField.text ?? "", password: passwordCell?.textField.text ?? "") { (oAuthState) in
buttonCell?.button.stopLoading()
switch oAuthState {
case .succsess:
break
case .needTwoFactor:
self.delegate?.needRequestCode()
break
default:
loginCell?.textField.isEnabled = true
passwordCell?.textField.isEnabled = true
passwordCell?.textField.becomeFirstResponder()
UIAlertController.show(
title: self.content.errorOauthTitle,
message: self.content.errorOauthSubtitle,
buttonTitle: self.content.errorOauthButtonTitle,
on: self
)
break
}
}
}
}
extension SPNativeLoginViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.superview == self.tableView.cellForRow(at: IndexPath.init(row: 0, section: 0))?.contentView {
if let passwordCell = self.tableView.cellForRow(at: IndexPath.init(row: 1, section: 0)) as? SPFormTextFiledTableViewCell {
passwordCell.textField.becomeFirstResponder()
return false
}
}
if textField.superview == self.tableView.cellForRow(at: IndexPath.init(row: 1, section: 0))?.contentView {
textField.resignFirstResponder()
self.enterAction()
return true
}
return true
}
}
protocol SPLoginControllerDelegate: class {
var loginContent: SPNativeLoginNavigationController.LoginContent {get}
func login(with login: String, password: String, complection: @escaping (SPOauthState)->())
func needRequestCode() -> ()
}
@@ -1,311 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPBaseTableViewController: SPStatusBarManagerTableViewController {
var activityIndicatorView = UIActivityIndicatorView.init()
var searchController: UISearchController?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
let layoutIfShowKeyboard = {
let height = size.height - (self.keyboardSize?.height ?? 0) - self.safeArea.top
self.emptyProposeView?.frame = CGRect.init(
x: 0, y: 0,
width: size.width * self.emptyProposeViewWidthFactor,
height: height
)
self.emptyProposeView?.center.x = size.width / 2
}
let layoutIfNotShowKeyboard = {
let height = size.height - self.safeArea.top - self.safeArea.bottom
self.emptyProposeView?.frame = CGRect.init(
x: 0, y: 0,
width: size.width * self.emptyProposeViewWidthFactor,
height: height
)
self.emptyProposeView?.center.x = size.width / 2
}
if self.isShowKeyboard {
if self.isAllowLayoutWithKeyboardEvents {
layoutIfShowKeyboard()
} else {
layoutIfNotShowKeyboard()
}
} else {
layoutIfNotShowKeyboard()
}
if let center = self.emptyProposeView?.center {
self.activityIndicatorView.center = center
} else {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: (size.height - self.safeArea.top - self.safeArea.bottom) / 2
)
}
}
//MARK: - Keyboard
var isShowKeyboard: Bool = false
var isAllowLayoutWithKeyboardEvents: Bool = false
var keyboardSize: CGSize? = nil
func keyboardWillShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardWillHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
@objc func keyboardWillShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillShow(duration: duration)
}
@objc func keyboardDidShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidShow(duration: duration)
}
@objc func keyboardWillHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillHide(duration: duration)
}
@objc func keyboardDidHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidHide(duration: duration)
}
func keyboardSize(from notification: NSNotification) -> CGSize? {
let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
return keyboardFrame?.cgRectValue.size
}
func keyboardAnimateDuration(from notification: NSNotification) -> TimeInterval? {
return notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval
}
//MARK: - Empty Propose View
var emptyProposeViewYTranslateFactor: CGFloat = 0.98
var emptyProposeViewWidthFactor: CGFloat = 0.7
private var emptyProposeView: UIView? {
didSet {
if self.emptyProposeView != nil {
self.view.addSubview(self.emptyProposeView!)
self.updateLayout(with: self.view.frame.size)
}
}
}
func setEmptyProposeView(_ view: UIView) {
self.emptyProposeView = view
self.hideEmptyProposeView(animated: false)
}
func hideEmptyProposeView(animated: Bool) {
let hideFunc = {
self.emptyProposeView?.isHidden = true
self.emptyProposeView?.alpha = 0
}
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 0
}, withComplection: {
hideFunc()
})
} else {
hideFunc()
}
}
func showEmptyProposeView(animated: Bool) {
self.emptyProposeView?.isHidden = false
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 1
})
} else {
self.emptyProposeView?.alpha = 1
}
}
//MARK: - Cache
private var cacheImages: [(link: String, image: UIImage)] = []
func toCache(link: String, image: UIImage?) {
if image == nil {
return
}
if self.fromCahce(link: link) == nil {
self.cacheImages.append((link: link, image: image!))
}
}
func fromCahce(link: String) -> UIImage? {
let cachedData = self.cacheImages.first(where: {
$0.link == link
})
return cachedData?.image
}
//MARK: - Reload Table View
func reloadTableView(animated: Bool, complection: @escaping ()->() = {}) {
if animated {
UIView.transition(
with: self.tableView,
duration: 0.3,
options: [.transitionCrossDissolve, UIView.AnimationOptions.beginFromCurrentState],
animations: {
self.tableView.reloadData()
}, completion: {(state) in
complection()
})
} else {
self.tableView.reloadData()
complection()
}
}
func startLoading() {
if self.tableView.isEmpty {
self.activityIndicatorView.startAnimating()
if self.refreshControl == nil {
self.tableView.isScrollEnabled = false
}
}
self.hideEmptyProposeView(animated: false)
}
func endLoading() {
self.activityIndicatorView.stopAnimating()
if self.tableView.isEmpty {
self.reloadTableView(animated: true)
} else {
self.reloadTableView(animated: false)
}
if self.tableView.isEmpty {
if self.refreshControl == nil {
self.tableView.isScrollEnabled = false
}
self.showEmptyProposeView(animated: true)
} else {
self.tableView.isScrollEnabled = true
self.hideEmptyProposeView(animated: true)
}
if self.refreshControl?.isRefreshing ?? false {
delay(0.12, closure: {
self.refreshControl?.endRefreshing()
})
}
}
//MARK: - Search
@available(iOS 11.0, *)
func addSearchController(placeholder: String, resultController: UIViewController?, searchResultsUpdater: UISearchResultsUpdating?, searchBarDelegate: UISearchBarDelegate?) {
self.searchController = UISearchController(searchResultsController: resultController)
self.searchController?.searchBar.placeholder = placeholder
self.searchController?.searchResultsUpdater = searchResultsUpdater
self.searchController?.searchBar.delegate = searchBarDelegate
navigationItem.searchController = searchController
definesPresentationContext = true
self.searchController?.searchBar.tintColor = UINavigationController.elementsColor
}
//MARK: - Other
@objc func dismiss(sender: Any) {
self.dismiss()
}
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
}
@@ -1,245 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPBaseViewController: SPStatusBarManagerViewController {
var activityIndicatorLayoutWithSafeArea: Bool = true
let activityIndicatorView = UIActivityIndicatorView()
override public func viewDidLoad() {
super.viewDidLoad()
self.activityIndicatorView.style = .white
self.activityIndicatorView.stopAnimating()
self.view.addSubview(self.activityIndicatorView)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
self.updateLayout(with: self.view.frame.size)
}
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override public func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
var contentHeight = size.height - self.safeArea.top
if self.isShowKeyboard {
contentHeight = contentHeight - (self.keyboardSize?.height ?? 0)
} else {
contentHeight = contentHeight - self.safeArea.bottom
}
let centerYPosition = self.safeArea.top + (contentHeight / 2)
if self.activityIndicatorLayoutWithSafeArea {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: centerYPosition
)
} else {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: (size.height - (self.keyboardSize?.height ?? 0)) / 2
)
}
var emptyProposeViewWidth = size.width * self.emptyProposeViewWidthFactor
if emptyProposeViewWidth < (375 * self.emptyProposeViewWidthFactor) {
emptyProposeViewWidth = size.width * 0.9
}
self.emptyProposeView?.frame = CGRect.init(
x: 0, y: 0,
width: emptyProposeViewWidth,
height: (size.height - self.safeArea.top - self.safeArea.bottom) * self.emptyProposeViewHeightFactor
)
self.emptyProposeView?.center = CGPoint.init(
x: size.width / 2,
y: centerYPosition * self.emptyProposeViewCenterYFactor
)
if self.isShowKeyboard {
self.emptyProposeView?.center = CGPoint.init(
x: size.width / 2,
y: centerYPosition
)
}
}
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
@objc func dismiss(sender: Any) {
self.dismiss()
}
//MARK: - keyboard
var isShowKeyboard: Bool = false
var keyboardSize: CGSize? = nil
func keyboardWillShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidShow(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardWillHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
func keyboardDidHide(duration: TimeInterval) {
SPAnimation.animate(duration, animations: {
self.updateLayout(with: self.view.frame.size)
})
}
@objc func keyboardWillShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillShow(duration: duration)
}
@objc func keyboardDidShow(notification: NSNotification) {
self.isShowKeyboard = true
self.keyboardSize = self.keyboardSize(from: notification)
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidShow(duration: duration)
}
@objc func keyboardWillHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardWillHide(duration: duration)
}
@objc func keyboardDidHide(notification: NSNotification) {
self.isShowKeyboard = false
self.keyboardSize = nil
let duration = self.keyboardAnimateDuration(from: notification) ?? 0.3
self.keyboardDidHide(duration: duration)
}
func keyboardSize(from notification: NSNotification) -> CGSize? {
let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
return keyboardFrame?.cgRectValue.size
}
func keyboardAnimateDuration(from notification: NSNotification) -> TimeInterval? {
return notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval
}
//MARK: - Search
@available(iOS 11.0, *)
func addSearchController(placeholder: String, resultController: UIViewController?, searchResultsUpdater: UISearchResultsUpdating?, searchBarDelegate: UISearchBarDelegate?) -> UISearchController {
let searchController = UISearchController(searchResultsController: resultController)
searchController.searchBar.placeholder = placeholder
searchController.searchResultsUpdater = searchResultsUpdater
searchController.searchBar.delegate = searchBarDelegate
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = searchController
definesPresentationContext = true
return searchController
}
//MARK: - Empty propose view
var emptyProposeViewCenterYFactor: CGFloat = 0.94
var emptyProposeViewWidthFactor: CGFloat = 0.7
var emptyProposeViewHeightFactor: CGFloat = 1
private var emptyProposeView: UIView? {
didSet {
if self.emptyProposeView != nil {
self.view.addSubview(self.emptyProposeView!)
self.updateLayout(with: self.view.frame.size)
}
}
}
func setEmptyProposeView(_ view: UIView) {
self.emptyProposeView = view
}
func hideEmptyProposeView(animated: Bool) {
let hideFunc = {
self.emptyProposeView?.isHidden = true
self.emptyProposeView?.alpha = 0
}
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 0
}, withComplection: {
hideFunc()
})
} else {
hideFunc()
}
}
func showEmptyProposeView(animated: Bool) {
self.emptyProposeView?.isHidden = false
if animated {
SPAnimation.animate(0.3, animations: {
self.emptyProposeView?.alpha = 1
})
} else {
self.emptyProposeView?.alpha = 1
}
}
}
@@ -1,420 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPConfirmActionViewController: UIViewController {
let navigationBarView = SPConfirmActionNavigationBar.init()
let cellsView = SPConfirmActionCellsView.init()
let confirmActionView = SPConfirmActionButtonView.init()
var confirmedAction: (_ controller: SPConfirmActionViewController)->() = { controller in
controller.hide()
}
private var animationDuration: TimeInterval {
return 0.5
}
override func viewDidLoad() {
super.viewDidLoad()
self.modalPresentationStyle = .overCurrentContext
self.modalTransitionStyle = .crossDissolve
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
self.confirmActionView.button.addTarget(self, action: #selector(self.tapConfirmButton), for: .touchUpInside)
self.navigationBarView.button.addTarget(self, action: #selector(self.hide), for: .touchUpInside)
self.view.addSubview(self.navigationBarView)
self.view.addSubview(self.cellsView)
self.view.addSubview(self.confirmActionView)
self.updateLayout(size: self.view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.updateLayout(size: size)
}
override func viewSafeAreaInsetsDidChange() {
if #available(iOS 11.0, *) {
super.viewSafeAreaInsetsDidChange()
self.updateLayout(size: self.view.frame.size)
}
}
private func updateLayout(size: CGSize) {
self.confirmActionView.setWidth(size.width)
self.confirmActionView.sizeToFit()
self.confirmActionView.frame.origin.x = 0
self.confirmActionView.frame.origin.y = size.height - self.confirmActionView.frame.height
self.cellsView.setWidth(size.width)
self.cellsView.sizeToFit()
self.cellsView.frame.origin.x = 0
self.cellsView.frame.origin.y = self.confirmActionView.frame.origin.y - self.cellsView.frame.height
self.navigationBarView.setWidth(size.width)
self.navigationBarView.sizeToFit()
self.navigationBarView.frame.origin.x = 0
self.navigationBarView.frame.origin.y = self.cellsView.frame.origin.y - self.navigationBarView.frame.height
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.confirmActionView.frame.origin.y = self.view.frame.size.height
self.cellsView.frame.origin.y = self.view.frame.size.height
self.navigationBarView.frame.origin.y = self.view.frame.size.height
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
self.confirmActionView.frame.origin.y = self.view.frame.height - self.confirmActionView.frame.height
self.cellsView.frame.origin.y = self.confirmActionView.frame.origin.y - self.cellsView.frame.height
self.navigationBarView.frame.origin.y = self.cellsView.frame.origin.y - self.navigationBarView.frame.height
}, spring: 1,
velocity: 1,
options: .transitionCurlUp)
}
func present(on viewController: UIViewController) {
self.modalPresentationStyle = .overCurrentContext
self.modalTransitionStyle = .crossDissolve
viewController.present(self, animated: false, completion: nil)
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0)
self.confirmActionView.frame.origin.y = self.view.frame.size.height
self.cellsView.frame.origin.y = self.view.frame.size.height
self.navigationBarView.frame.origin.y = self.view.frame.size.height
}, spring: 1,
velocity: 1,
options: .transitionCurlDown,
withComplection: {
super.dismiss(animated: false) {
completion?()
}})
}
func addCell(task: String, title: String, subtitle: String? = nil, imageLink: String? = nil, image: UIImage? = nil) {
let cell = SPConfirmActionCellView.init(
task: task,
title: title,
subtitle: subtitle,
imageLink: imageLink,
image: image
)
self.cellsView.addSubview(cell)
}
@objc func hide() {
self.dismiss(animated: true)
}
@objc func tapConfirmButton() {
self.confirmedAction(self)
}
class SPConfirmActionNavigationBar: UIView {
let label: UILabel = UILabel.init()
let button: UIButton = UIButton.init()
let separatorView = UIView.init()
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
private var leftAndRightSpace: CGFloat {
return 18
}
init() {
super.init(frame: CGRect.zero)
self.addSubview(self.backgroundView)
self.separatorView.backgroundColor = UIColor.black.withAlphaComponent(0.15)
self.addSubview(self.separatorView)
self.label.numberOfLines = 1
self.label.textColor = UIColor.black
self.label.text = "SPConfirmAction"
self.label.font = UIFont.system(type: .DemiBold, size: 16)
self.addSubview(self.label)
self.button.setTitle("Сancel", for: .normal)
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
self.addSubview(self.button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
self.setHeight(45)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundView.setEqualsBoundsFromSuperview()
self.label.sizeToFit()
self.label.frame.origin.x = self.leftAndRightSpace
self.label.center.y = self.frame.height / 2
self.button.sizeToFit()
self.button.frame.origin.x = self.frame.width - self.leftAndRightSpace - self.button.frame.width
self.button.center.y = self.frame.height / 2
self.separatorView.frame = CGRect.init(x: 0, y: self.frame.height - 1, width: self.frame.width, height: 1)
}
}
class SPConfirmActionCellView: UIView {
var taskLabel = UILabel.init()
var titleLabel: UILabel = UILabel.init()
var subtitleLabel: UILabel?
var imageView: SPDownloadingImageView?
var separatorView = UIView.init()
var yLeftPosition: CGFloat = 0
var baseSpace: CGFloat = 0
init(task: String, title: String, subtitle: String? = nil, imageLink: String? = nil, image: UIImage? = nil) {
super.init(frame: CGRect.zero)
self.separatorView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.3)
self.addSubview(self.separatorView)
self.taskLabel.text = task.uppercased()
self.taskLabel.font = UIFont.system(type: .Medium, size: 13)
self.taskLabel.textColor = SPNativeStyleKit.Colors.gray
self.taskLabel.numberOfLines = 1
self.taskLabel.textAlignment = .right
self.addSubview(self.taskLabel)
self.titleLabel.text = title.uppercased()
self.titleLabel.font = UIFont.system(type: .Medium, size: 13)
self.titleLabel.textColor = UIColor.black
self.titleLabel.numberOfLines = 1
self.titleLabel.textAlignment = .left
self.addSubview(self.titleLabel)
if subtitle != nil {
self.subtitleLabel = UILabel.init()
self.subtitleLabel?.text = subtitle?.uppercased()
self.subtitleLabel?.font = UIFont.system(type: .Medium, size: 13)
self.subtitleLabel?.textColor = SPNativeStyleKit.Colors.gray
self.subtitleLabel?.numberOfLines = 0
self.subtitleLabel?.textAlignment = .left
self.addSubview(self.subtitleLabel!)
}
if imageLink != nil {
self.imageView = SPDownloadingImageView.init()
self.imageView?.setImage(link: imageLink!)
self.imageView?.layer.cornerRadius = 12
self.addSubview(self.imageView!)
}
if image != nil {
self.imageView = SPDownloadingImageView.init()
self.imageView?.setImage(image: image!, animatable: true)
self.imageView?.layer.cornerRadius = 12
self.addSubview(self.imageView!)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
self.setHeight(45)
if self.imageView != nil {
self.setHeight(75)
}
if self.subtitleLabel != nil {
var height = 45 + self.subtitleLabel!.frame.height
height.setIfFewer(when: 75)
self.setHeight(height)
}
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.taskLabel.sizeToFit()
self.taskLabel.center.y = self.frame.height / 2
self.taskLabel.frame.origin.x = self.yLeftPosition - self.taskLabel.frame.width
self.titleLabel.sizeToFit()
self.titleLabel.center.y = self.frame.height / 2
self.titleLabel.frame.origin.x = self.yLeftPosition + self.baseSpace
self.titleLabel.setWidth(self.frame.width - self.titleLabel.frame.origin.x - self.baseSpace)
if subtitleLabel != nil {
let allContentHeight: CGFloat = self.titleLabel.frame.height + self.subtitleLabel!.frame.height + 3
self.titleLabel.frame.origin.y = (self.frame.height - allContentHeight) / 2
self.subtitleLabel?.frame.origin.x = self.titleLabel.frame.origin.x
self.subtitleLabel?.frame.origin.y = self.titleLabel.frame.bottomYPosition + 3
self.subtitleLabel?.sizeToFit()
self.subtitleLabel?.setWidth(self.titleLabel.frame.width)
}
if imageView != nil {
self.taskLabel.frame = CGRect.zero
var imageSideSize = self.frame.height - 10 * 2
imageSideSize.setIfMore(when: 40)
self.imageView?.frame = CGRect.init(x: self.yLeftPosition - imageSideSize, y: 0, width: imageSideSize, height: imageSideSize)
self.imageView?.center.y = self.frame.height / 2
}
self.separatorView.frame = CGRect.init(x: 18, y: self.frame.height - 1, width: self.frame.width - 18, height: 1)
}
}
class SPConfirmActionCellsView: UIView {
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
private var baseSpace: CGFloat = 18
init() {
super.init(frame: CGRect.zero)
self.addSubview(self.backgroundView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundView.setEqualsBoundsFromSuperview()
var maxLabelWidth: CGFloat = 0
for view in self.subviews {
if view != self.backgroundView {
if let cellView = view as? SPConfirmActionCellView {
cellView.sizeToFit()
if maxLabelWidth < cellView.taskLabel.frame.width {
maxLabelWidth = cellView.taskLabel.frame.width
}
}
}
}
for view in self.subviews {
if view != self.backgroundView {
if let cellView = view as? SPConfirmActionCellView {
cellView.yLeftPosition = self.baseSpace * 2 + maxLabelWidth
cellView.baseSpace = self.baseSpace
cellView.layoutSubviews()
}
}
}
var yPosition: CGFloat = 0
for view in subviews {
if view != self.backgroundView {
view.sizeToFit()
view.setWidth(self.frame.width)
view.frame.origin = CGPoint.init(x: 0, y: yPosition)
yPosition += view.frame.height
}
}
self.setHeight(yPosition)
}
override func sizeToFit() {
super.sizeToFit()
var height: CGFloat = 0
for view in subviews {
if view != self.backgroundView {
view.sizeToFit()
height += view.frame.height
}
}
self.setHeight(height)
self.layoutSubviews()
}
}
class SPConfirmActionButtonView: UIView {
let button = SPAppStoreActionButton()
let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
init() {
super.init(frame: CGRect.zero)
self.button.style = .buyInStore
self.button.setTitle("Execute", for: .normal)
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 16)
self.addSubview(self.backgroundView)
self.addSubview(self.button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeToFit() {
super.sizeToFit()
var baseHeight: CGFloat = 80
if #available(iOS 11.0, *) {
baseHeight += (self.superview?.safeAreaInsets.bottom ?? 0)
}
self.setHeight(baseHeight)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundView.setEqualsBoundsFromSuperview()
self.button.sizeToFit()
self.button.setXCenteringFromSuperview()
var safeAreaInsetsBottom: CGFloat = 0
if #available(iOS 11.0, *) {
safeAreaInsetsBottom = (self.superview?.safeAreaInsets.bottom ?? 0)
}
self.button.center.y = (self.frame.height - safeAreaInsetsBottom) / 2
}
}
}
@@ -0,0 +1,64 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
public class SPController: SPStatusBarManagerController {
let emptyTitlesView = SPEmptyTitlesView(title: "No Data", subtitle: "No data or information")
override public func viewDidLoad() {
super.viewDidLoad()
self.emptyTitlesView.isHidden = true
self.view.addSubview(self.emptyTitlesView)
self.updateLayout(with: self.view.frame.size)
}
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override public func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
self.emptyTitlesView.layout(centerY: size.height / 2)
}
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
@objc func dismiss(sender: Any) {
self.dismiss()
}
}
@@ -21,7 +21,7 @@
import UIKit
class SPNativeTableViewController: SPBaseTableViewController {
class SPNativeTableController: SPTableController {
let labelTableViewCellIdentifier: String = "labelTableViewCellIdentifier"
let textFieldTableViewCellIdentifier: String = "textFieldTableViewCellIdentifier"
@@ -31,7 +31,6 @@ class SPNativeTableViewController: SPBaseTableViewController {
let promoTableViewCellIdentifier: String = "promoTableViewCellIdentifier"
let featuredTitleTableViewCellIdentifier: String = "featuredTitleTableViewCellIdentifier"
let mailTableViewCellIdentifier: String = "mailTableViewCellIdentifier"
let collectionImagesTableViewCellIdentifier: String = "collectionImagesTableViewCellIdentifier"
let imageTableViewCellIdentifier: String = "imageTableViewCellIdentifier"
let proposeTableViewCellIdentifier: String = "proposeTableViewCellIdentifier"
let mengTransformTableViewCell = "mengTransformTableViewCell"
@@ -40,16 +39,9 @@ class SPNativeTableViewController: SPBaseTableViewController {
var showBottomInsets: Bool = true
var autoTopSpace: Bool = true
var autoBottomSpace: Bool = true
private var autoSpaceHeight: CGFloat = 35
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
@@ -62,7 +54,7 @@ class SPNativeTableViewController: SPBaseTableViewController {
self.tableView.contentInsetAdjustmentBehavior = .always
}
self.tableView.backgroundColor = SPNativeStyleKit.Colors.customGray
self.tableView.backgroundColor = SPNativeColors.customGray
self.tableView.delaysContentTouches = false
self.tableView.allowsSelection = false
self.tableView.rowHeight = UITableView.automaticDimension
@@ -78,13 +70,12 @@ class SPNativeTableViewController: SPBaseTableViewController {
self.tableView.register(SPPromoTableViewCell.self, forCellReuseIdentifier: self.promoTableViewCellIdentifier)
self.tableView.register(SPFormFeaturedTitleTableViewCell.self, forCellReuseIdentifier: self.featuredTitleTableViewCellIdentifier)
self.tableView.register(SPFormMailTableViewCell.self, forCellReuseIdentifier: self.mailTableViewCellIdentifier)
self.tableView.register(SPCollectionImagesTableViewCell.self, forCellReuseIdentifier: self.collectionImagesTableViewCellIdentifier)
self.tableView.register(SPImageTableViewCell.self, forCellReuseIdentifier: self.imageTableViewCellIdentifier)
self.tableView.register(SPProposeTableViewCell.self, forCellReuseIdentifier: self.proposeTableViewCellIdentifier)
self.tableView.register(SPMengTransformTableViewCell.self, forCellReuseIdentifier: self.mengTransformTableViewCell)
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeStyleKit.Colors.gray
self.activityIndicatorView.color = SPNativeColors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
@@ -150,10 +141,6 @@ class SPNativeTableViewController: SPBaseTableViewController {
return cell
}
func dequeueCollectionImagesTableViewCell(indexPath: IndexPath) -> SPCollectionImagesTableViewCell {
return tableView.dequeueReusableCell(withIdentifier: self.collectionImagesTableViewCellIdentifier, for: indexPath as IndexPath) as! SPCollectionImagesTableViewCell
}
func dequeueImageTableViewCell(indexPath: IndexPath) -> SPImageTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.imageTableViewCellIdentifier, for: indexPath as IndexPath) as! SPImageTableViewCell
cell.currentIndexPath = indexPath
@@ -170,7 +157,7 @@ class SPNativeTableViewController: SPBaseTableViewController {
}
//MARK: - manage selection
extension SPNativeTableViewController {
extension SPNativeTableController {
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
@@ -184,7 +171,7 @@ extension SPNativeTableViewController {
}
//MARK: - manage spaces
extension SPNativeTableViewController {
extension SPNativeTableController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
@@ -21,10 +21,11 @@
import UIKit
class SPProposeViewController: SPBaseViewController {
class SPProposeController: SPController {
private let data: Data
internal let areaView = AreaView()
private var isPresent: Bool = false
private var animationDuration: TimeInterval {
return 0.5
@@ -78,28 +79,24 @@ class SPProposeViewController: SPBaseViewController {
self.updateLayout(with: self.view.frame.size)
}
override func updateLayout(with size: CGSize) {
self.areaView.setWidth(size.width - (self.space * 2))
self.areaView.layoutSubviews()
self.areaView.sizeToFit()
self.areaView.frame.origin.x = self.space
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.isPresent {
self.present()
self.isPresent = true
}
}
func present(on viewController: UIViewController) {
viewController.present(self, animated: false, completion: {
SPVibration.impact(SPVibration.Mode.warning)
self.areaView.frame.origin.y = self.view.frame.size.height
self.areaView.isHidden = false
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
}, spring: 1,
velocity: 1,
options: .transitionCurlUp)
})
private func present() {
SPVibration.impact(system: .warning)
self.areaView.frame.origin.y = self.view.frame.size.height
self.areaView.isHidden = false
SPAnimationSpring.animate(self.animationDuration, animations: {
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
}, spring: 1,
velocity: 1,
options: .transitionCurlUp)
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
@@ -130,8 +127,16 @@ class SPProposeViewController: SPBaseViewController {
}
}
@objc func handleGesture(sender: UIPanGestureRecognizer) {
override func updateLayout(with size: CGSize) {
self.areaView.setWidth(size.width - (self.space * 2))
self.areaView.layoutSubviews()
self.areaView.sizeToFit()
self.areaView.frame.origin.x = self.space
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
}
@objc func handleGesture(sender: UIPanGestureRecognizer) {
let returnAreaViewToPoint = {
SPAnimationSpring.animate(self.animationDuration, animations: {
self.areaView.frame.origin.y = self.view.frame.size.height - self.areaView.frame.height - (self.bottomSafeArea / 2) - self.space
@@ -195,7 +200,7 @@ class SPProposeViewController: SPBaseViewController {
self.addSubview(self.titleLabel)
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 16)
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.black
self.subtitleLabel.textColor = SPNativeColors.black
self.subtitleLabel.numberOfLines = 0
self.subtitleLabel.setCenteringAlignment()
self.addSubview(self.subtitleLabel)
@@ -206,7 +211,7 @@ class SPProposeViewController: SPBaseViewController {
self.addSubview(self.imageView)
self.button.titleLabel?.font = UIFont.system(type: UIFont.BoldType.Medium, size: 15)
self.button.setTitleColor(SPNativeStyleKit.Colors.black)
self.button.setTitleColor(SPNativeColors.black)
self.button.backgroundColor = UIColor.init(hex: "D4D3DB")
self.addSubview(self.button)
@@ -21,7 +21,7 @@
import UIKit
public class SPStatusBarManagerViewController: UIViewController {
public class SPStatusBarManagerController: UIViewController {
var statusBar: SPStatusBar = .dark {
didSet {
@@ -57,7 +57,7 @@ public class SPStatusBarManagerViewController: UIViewController {
}
}
public class SPStatusBarManagerTableViewController: UITableViewController {
public class SPStatusBarManagerTableController: UITableViewController {
var statusBar: SPStatusBar = .dark {
didSet {
@@ -0,0 +1,108 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPTableController: SPStatusBarManagerTableController {
var activityIndicatorView = UIActivityIndicatorView.init()
override func viewDidLoad() {
super.viewDidLoad()
self.activityIndicatorView.stopAnimating()
self.activityIndicatorView.color = SPNativeColors.gray
self.view.addSubview(self.activityIndicatorView)
self.updateLayout(with: self.view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (contex) in
self.updateLayout(with: size)
}, completion: nil)
}
@available(iOS 11.0, *)
override func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
self.activityIndicatorView.center = CGPoint.init(
x: size.width / 2,
y: (size.height - self.safeArea.top - self.safeArea.bottom) / 2
)
}
//MARK: - Cache
private var cacheImages: [(link: String, image: UIImage)] = []
func toCache(link: String, image: UIImage?) {
if image == nil {
return
}
if self.fromCahce(link: link) == nil {
self.cacheImages.append((link: link, image: image!))
}
}
func fromCahce(link: String) -> UIImage? {
let cachedData = self.cacheImages.first(where: {
$0.link == link
})
return cachedData?.image
}
//MARK: - Reload Table View
func reloadTableView(animated: Bool, complection: @escaping ()->() = {}) {
if animated {
UIView.transition(
with: self.tableView,
duration: 0.3,
options: [.transitionCrossDissolve, UIView.AnimationOptions.beginFromCurrentState],
animations: {
self.tableView.reloadData()
}, completion: {(state) in
complection()
})
} else {
self.tableView.reloadData()
complection()
}
}
//MARK: - Other
@objc func dismiss(sender: Any) {
self.dismiss()
}
func addHideButton(title: String) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(
title: title,
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(self.dismiss(sender:))
)
}
}
@@ -1,218 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPWelcomeViewController: SPBaseViewController {
let imageView = SPDownloadingImageView()
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let descriptionLabel = UILabel()
let commentLabel = UILabel()
let button = SPNativeLargeButton()
private var data: SPWelcomeData
private var views: [UIView] = []
private var isPresented: Bool = false
static var isNeedPresent: Bool {
return self.isFirstLaunch || self.hasBeenUpdated
}
init(data: SPWelcomeData) {
self.data = data
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
self.data = SPWelcomeData()
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
self.statusBar = self.data.statusBarStyle
self.view.backgroundColor = self.data.backgroundColor
self.imageView.setImage(image: self.data.image, animatable: false)
self.imageView.contentMode = .scaleAspectFit
self.imageView.layer.cornerRadius = 6
self.imageView.layer.masksToBounds = true
self.view.addSubview(self.imageView)
self.views.append(self.imageView)
self.titleLabel.text = self.data.title
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Heavy, size: 50)
self.titleLabel.numberOfLines = 1
self.titleLabel.adjustsFontSizeToFitWidth = true
self.titleLabel.minimumScaleFactor = 0.5
self.titleLabel.textColor = self.data.textColor
self.titleLabel.setLettersSpacing(-2)
self.view.addSubview(self.titleLabel)
self.views.append(self.titleLabel)
self.subtitleLabel.text = self.data.subtitle
self.subtitleLabel.font = UIFont.system(type: UIFont.BoldType.Heavy, size: 50)
self.subtitleLabel.textColor = self.data.color
self.subtitleLabel.numberOfLines = 1
self.subtitleLabel.adjustsFontSizeToFitWidth = true
self.subtitleLabel.minimumScaleFactor = 0.5
self.subtitleLabel.setLettersSpacing(-2)
self.view.addSubview(self.subtitleLabel)
self.views.append(self.subtitleLabel)
self.descriptionLabel.text = self.data.description
if SPWelcomeViewController.hasBeenUpdated {
self.descriptionLabel.text = self.data.changes
}
self.descriptionLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 17)
self.descriptionLabel.textColor = self.data.textColor
self.descriptionLabel.numberOfLines = 0
self.descriptionLabel.setLettersSpacing(-0.2)
self.view.addSubview(self.descriptionLabel)
self.views.append(self.descriptionLabel)
self.commentLabel.text = self.data.comment
self.commentLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 13)
self.commentLabel.textColor = UIColor.gray
self.commentLabel.numberOfLines = 0
self.commentLabel.setCenteringAlignment()
self.view.addSubview(self.commentLabel)
self.views.append(self.commentLabel)
self.button.setTitle(self.data.buttonTitle, for: UIControl.State.normal)
self.button.backgroundColor = self.data.color
self.button.addTarget(self, action: #selector(self.tapButton(sender:)), for: UIControl.Event.touchUpInside)
self.view.addSubview(self.button)
self.views.append(self.button)
for view in self.views {
view.alpha = 0
}
self.updateLayout(with: self.view.frame.size)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.isPresented {
SPAnimationAlpha.showList(0.5, views: self.views, delayPerItem: 0.09)
self.isPresented = true
SPWelcomeViewController.updateCurrentVersion()
}
}
@objc func tapButton(sender: UIButton) {
SPVibration.impact(SPVibration.Style.light)
self.data.complection(self.button)
}
override func updateLayout(with size: CGSize) {
super.updateLayout(with: size)
let sideSpace: CGFloat = size.width * 0.112
self.imageView.frame = CGRect.init(x: sideSpace, y: self.safeArea.top + (size.height * 0.1), width: 84, height: 84)
let space: CGFloat = size.height * 0.025
self.titleLabel.sizeToFit()
self.titleLabel.frame = CGRect.init(x: sideSpace, y: self.imageView.frame.bottomYPosition + space, width: self.view.frame.width - sideSpace * 2, height: self.titleLabel.frame.height)
self.subtitleLabel.sizeToFit()
self.subtitleLabel.frame = CGRect.init(x: sideSpace, y: self.titleLabel.frame.bottomYPosition - 12, width: self.view.frame.width - sideSpace * 2, height: self.subtitleLabel.frame.height)
self.descriptionLabel.frame = CGRect.init(x: sideSpace, y: self.subtitleLabel.frame.bottomYPosition + space, width: self.view.frame.width - sideSpace * 2, height: self.descriptionLabel.frame.height)
self.descriptionLabel.sizeToFit()
self.button.sizeToFit()
self.button.frame.origin.y = size.height - self.safeArea.bottom - space - self.button.frame.height
self.button.setWidth(size.width - sideSpace * 2)
self.button.center.x = size.width / 2
let sideSpaceForCommentLabel: CGFloat = sideSpace * 1.5
self.commentLabel.frame = CGRect.init(x: sideSpaceForCommentLabel, y: 0, width: self.view.frame.width - sideSpaceForCommentLabel * 2, height: 0)
self.commentLabel.sizeToFit()
self.commentLabel.center.x = size.width / 2
self.commentLabel.frame.origin.y = self.button.frame.origin.y - space - self.commentLabel.frame.height
}
}
extension SPWelcomeViewController {
static private let versionKey = "SPWelcomeControllerCurrentVersionKey"
static private let firstLaucnhKey = "SPWelcomeControllerFirstLaucnhKey"
static var isFirstLaunch: Bool {
get {
let value = UserDefaults.standard.value(forKey: self.firstLaucnhKey) as? Bool ?? true
if value {
SPWelcomeViewController.updateCurrentVersion()
}
self.isFirstLaunch = false
return value
}
set {
UserDefaults.standard.set(newValue, forKey: self.firstLaucnhKey)
}
}
static var hasBeenUpdated: Bool {
let userDefaults = UserDefaults.standard
var bundleInfo = Bundle.main.infoDictionary!
if let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String {
if let version = userDefaults.value(forKey: self.versionKey) as? String {
return !(version == currentVersion)
} else {
return false
}
}
return false
}
private static func updateCurrentVersion() {
var bundleInfo = Bundle.main.infoDictionary!
if let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String {
UserDefaults.standard.set(currentVersion, forKey: self.versionKey)
}
}
}
struct SPWelcomeData {
var title: String = "Welcome to"
var subtitle: String = "Sparrow"
var description: String = "Simple and native welcome controller for all developers"
var changes: String = "Description about this update"
var comment: String = "Here you can place comment"
var buttonTitle: String = "Continue"
var image: UIImage = UIImage()
var color: UIColor = SPNativeStyleKit.Colors.blue
var backgroundColor: UIColor = UIColor.white
var textColor: UIColor = UIColor.black
var statusBarStyle: SPStatusBar = .dark
var complection: (_ button: SPNativeLargeButton) -> () = { _ in }
}
@@ -29,7 +29,7 @@ class SPAudioIconView: UIView {
}
}
var color = SPNativeStyleKit.Colors.white {
var color = SPNativeColors.white {
didSet {
self.setNeedsDisplay()
}
@@ -29,7 +29,7 @@ class SPGolubevIconView: UIView {
}
}
var whiteColor = SPNativeStyleKit.Colors.white {
var whiteColor = SPNativeColors.white {
didSet {
self.setNeedsDisplay()
}
@@ -29,7 +29,7 @@ class SPSocialIconView: UIView {
}
}
var color = SPNativeStyleKit.Colors.blue {
var color = SPNativeColors.blue {
didSet {
self.setNeedsDisplay()
}
@@ -29,7 +29,7 @@ class SPSystemIconView: UIView {
}
}
var color = SPNativeStyleKit.Colors.blue {
var color = SPNativeColors.blue {
didSet {
self.setNeedsDisplay()
}
@@ -86,8 +86,8 @@ class SPMengTransformCollectionViewCell: SPCollectionViewCell {
self.backgroundImageView.bottomAnchor.constraint(equalTo:
contentView.bottomAnchor, constant: 0).isActive = true
self.gradientView.setStartColorPosition(SPGradientView.Position.TopLeft)
self.gradientView.setEndColorPosition(.BottomRight)
self.gradientView.startColorPosition = .TopLeft
self.gradientView.endColorPosition = .BottomRight
self.gradientView.isHidden = true
self.gradientView.translatesAutoresizingMaskIntoConstraints = false
self.gradientView.layer.masksToBounds = false
@@ -196,7 +196,6 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
if isAllowInsertAnimation {
if self.insertIndexPaths.contains(itemIndexPath) {
//attributes?.center = CGPoint.init(x: attributes?.center.x ?? 0, y: 40)
attributes?.alpha = 0
attributes?.zIndex = 0
attributes?.transform = CGAffineTransform.init(scaleX: self.minimumScaleFactor, y: self.minimumScaleFactor)
@@ -207,11 +206,10 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
}
override public func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
var attributes: UICollectionViewLayoutAttributes?
let attributes: UICollectionViewLayoutAttributes? = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
if self.deleteIndexPaths.contains(itemIndexPath) {
attributes = self.layoutAttributesForItem(at: itemIndexPath)
attributes?.alpha = 0
attributes?.zIndex = 0
attributes?.transform3D = CATransform3DScale(CATransform3DIdentity, self.minimumScaleFactor, self.minimumScaleFactor, 1)
@@ -229,9 +227,19 @@ public class SPCollectionViewLayout: UICollectionViewFlowLayout {
collectionView.decelerationRate = UIScrollView.DecelerationRate.fast
if cellSideRatio == nil {
var height = collectionView.bounds.size.height * self.heightFactor
if height > self.maxHeight {
height = self.maxHeight
}
var width = collectionView.bounds.size.width * self.widthFactor
if width > self.maxWidth {
width = self.maxWidth
}
self.itemSize = CGSize.init(
width: collectionView.bounds.size.width * self.widthFactor,
height: collectionView.bounds.size.height * self.heightFactor
width: width,
height: height
)
} else {
self.itemSize = SPLayout.sizeWith(
@@ -1,91 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPImagesLineCollectionView: SPCollectionView {
fileprivate let imageCellIdentificator: String = "imageCellIdentificator"
var links: [String] = []
var images: [UIImage] = []
var mode: Mode = .links {
didSet {
self.reloadData()
}
}
override init() {
super.init()
self.layout.scrollDirection = .horizontal
self.dataSource = self
self.register(SPImageCollectionViewCell.self, forCellWithReuseIdentifier: self.imageCellIdentificator)
self.layout.cellSideRatio = 1
self.layout.heightFactor = 1
self.layout.minItemSpace = 10
self.layout.maxItemSpace = 10
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
enum Mode {
case links
case images
}
}
extension SPImagesLineCollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch self.mode {
case .links:
return self.links.count
case .images:
return self.images.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = dequeueReusableCell(withReuseIdentifier: self.imageCellIdentificator, for: indexPath) as! SPImageCollectionViewCell
switch self.mode {
case .links:
let link = self.links[indexPath.row]
if let image = self.fromCahce(link: link) {
cell.view.setImage(image: image, animatable: false)
} else {
cell.view.setImage(link: link) { (image) in
self.toCache(link: link, image: image)
}
}
case .images:
cell.view.setImage(image: self.images[indexPath.row], animatable: false)
}
return cell
}
}
@@ -0,0 +1,74 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPSectionLabelsView: SPView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let button = UIButton.init(type: UIButton.ButtonType.system)
override func commonInit() {
super.commonInit()
self.titleLabel.font = UIFont.system(type: .Bold, size: 23)
self.titleLabel.textAlignment = .left
self.titleLabel.textColor = UIColor.black
self.titleLabel.numberOfLines = 0
self.addSubview(self.titleLabel)
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 17)
self.subtitleLabel.textAlignment = .left
self.subtitleLabel.textColor = UIColor.black.withAlphaComponent(0.7)
self.subtitleLabel.numberOfLines = 0
self.addSubview(self.subtitleLabel)
self.button.titleLabel?.font = UIFont.system(type: .Regular, size: 17)
self.button.setTitleColor(SPNativeColors.blue, for: UIControl.State.normal)
self.addSubview(self.button)
}
func layout(origin: CGPoint, width: CGFloat) {
self.frame.origin = origin
self.setWidth(width)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.titleLabel.sizeToFit()
self.titleLabel.setWidth(self.frame.width)
self.titleLabel.frame.origin = CGPoint.zero
self.subtitleLabel.sizeToFit()
self.subtitleLabel.setWidth(self.frame.width)
self.subtitleLabel.frame.origin.x = 0
self.subtitleLabel.frame.origin.y = self.titleLabel.frame.bottomYPosition + 3
self.button.sizeToFit()
self.button.frame.bottomXPosition = self.frame.width
self.button.frame.bottomYPosition = self.titleLabel.frame.bottomYPosition
self.setHeight(self.subtitleLabel.frame.bottomYPosition)
}
}
@@ -0,0 +1,76 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPEmptyTitlesView: SPView {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
init(title: String, subtitle: String) {
self.titleLabel.text = title
self.subtitleLabel.text = subtitle
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func commonInit() {
super.commonInit()
self.titleLabel.font = UIFont.system(type: .Bold, size: 29)
self.titleLabel.textColor = SPNativeColors.gray
self.titleLabel.setCenteringAlignment()
self.titleLabel.numberOfLines = 0
self.addSubview(self.titleLabel)
self.subtitleLabel.font = UIFont.system(type: .Regular, size: 17)
self.subtitleLabel.textColor = SPNativeColors.gray
self.subtitleLabel.setCenteringAlignment()
self.subtitleLabel.numberOfLines = 0
self.addSubview(self.subtitleLabel)
}
func layout(centerY: CGFloat) {
if let superview = self.superview {
self.setWidth(superview.frame.width * 0.7)
self.layoutSubviews()
self.center = CGPoint.init(x: superview.frame.width / 2, y: centerY)
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.titleLabel.frame.origin = .zero
self.titleLabel.sizeToFit()
self.titleLabel.setWidth(self.frame.width)
self.subtitleLabel.frame.origin = CGPoint.init(x: 0, y: self.titleLabel.frame.bottomYPosition + 5)
self.subtitleLabel.sizeToFit()
self.subtitleLabel.setWidth(self.frame.width)
self.setHeight(self.subtitleLabel.frame.bottomYPosition)
}
}
@@ -54,6 +54,21 @@ public class SPFakeBarView: UIView {
}
}
public var closeButton: closeButtonPlace = .none {
didSet {
self.leftButton.titleLabel?.font = UIFont.system(type: .Regular, size: 17)
self.rightButton.titleLabel?.font = UIFont.system(type: .Regular, size: 17)
switch self.closeButton {
case .left:
self.leftButton.titleLabel?.font = UIFont.system(type: .DemiBold, size: 17)
case .right:
self.rightButton.titleLabel?.font = UIFont.system(type: .DemiBold, size: 17)
case .none:
break
}
}
}
public var titleLabel = UILabel.init()
public var subtitleLabel = UILabel.init()
public var leftButton = UIButton.init()
@@ -130,6 +145,8 @@ public class SPFakeBarView: UIView {
self.rightButton.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
self.rightButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12).isActive = true
self.closeButton = .none
self.setContraints()
self.updateStyle()
}
@@ -197,5 +214,11 @@ public class SPFakeBarView: UIView {
self.heightConstraint?.constant = self.height
self.updateConstraints()
}
public enum closeButtonPlace {
case left
case right
case none
}
}
@@ -0,0 +1,91 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPFooterActionsView: SPView {
var sectionLabels = SPSectionLabelsView()
private var buttons: [SPFooterActionButton] = []
private var separators: [SPSeparatorView] = []
override func commonInit() {
super.commonInit()
self.backgroundColor = UIColor.clear
self.sectionLabels.titleLabel.text = "Actions"
self.addSubview(self.sectionLabels)
}
func addButton(title: String, titleColor: UIColor, target: @escaping ()->()) {
let button = SPFooterActionButton()
button.setTitle(title)
button.setTitleColor(titleColor)
button.target { target() }
self.buttons.append(button)
self.addSubview(button)
let separator = SPSeparatorView()
self.separators.append(separator)
self.addSubview(separator)
}
func layout(origin: CGPoint, width: CGFloat) {
self.frame.origin = origin
self.setWidth(width)
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
self.sectionLabels.layout(origin: CGPoint.zero, width: self.frame.width)
let buttonHeight: CGFloat = 50
var yPositionButton: CGFloat = self.sectionLabels.frame.bottomYPosition + 12
if !self.buttons.isEmpty {
for i in 0...(buttons.count - 1) {
let button = self.buttons[i]
let separator = self.separators[i]
separator.frame.origin.x = 0
separator.frame.origin.y = yPositionButton
separator.setWidth(self.frame.width)
button.frame = CGRect.init(x: 0, y: yPositionButton, width: self.frame.width, height: buttonHeight)
yPositionButton += buttonHeight
}
self.setHeight(yPositionButton)
}
}
}
class SPFooterActionButton: SPButton {
override func commonInit() {
super.commonInit()
self.setTitleColor(SPNativeColors.blue)
self.titleLabel?.font = UIFont.system(type: .Regular, size: 21)
self.contentHorizontalAlignment = .left
}
}
@@ -23,21 +23,12 @@ import UIKit
public class SPGradientView: SPView {
var startColor: UIColor = UIColor.white { didSet { self.updateGradient() }}
var endColor: UIColor = UIColor.black { didSet { self.updateGradient() }}
var startColorPoint: CGPoint = CGPoint.zero { didSet { self.updateGradient() }}
var endColorPoint: CGPoint = CGPoint.zero { didSet { self.updateGradient() }}
var gradientLayer: CAGradientLayer = CAGradientLayer()
public func setStartColorPosition(_ position: Position) {
self.startColorPoint = getPointForPosition(position)
}
public func setEndColorPosition(_ position: Position) {
self.endColorPoint = getPointForPosition(position)
}
var startColor: UIColor = UIColor.white { didSet { self.updateGradient() }}
var endColor: UIColor = UIColor.black { didSet { self.updateGradient() }}
var startColorPosition: Position = Position.TopLeft { didSet { self.updateGradient() }}
var endColorPosition: Position = Position.BottomRight { didSet { self.updateGradient() }}
override func commonInit() {
super.commonInit()
@@ -47,8 +38,8 @@ public class SPGradientView: SPView {
private func updateGradient() {
self.gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
self.gradientLayer.locations = [0.0, 1.0]
self.gradientLayer.startPoint = self.startColorPoint
self.gradientLayer.endPoint = self.endColorPoint
self.gradientLayer.startPoint = self.startColorPosition.point
self.gradientLayer.endPoint = self.endColorPosition.point
}
override public func layoutSublayers(of layer: CALayer) {
@@ -65,49 +56,26 @@ public class SPGradientView: SPView {
case BottomRight
case MediumLeft
case MediumRight
}
private func getPointForPosition(_ position: Position) -> CGPoint {
switch position {
case .TopLeft:
return CGPoint.init(x: 0, y: 0)
case .TopCenter:
return CGPoint.init(x: 0.5, y: 0)
case .TopRight:
return CGPoint.init(x: 1, y: 0)
case .BottomLeft:
return CGPoint.init(x: 0, y: 1)
case .BottomCenter:
return CGPoint.init(x: 0.5, y: 1)
case .BottomRight:
return CGPoint.init(x: 1, y: 1)
case .MediumLeft:
return CGPoint.init(x: 0, y: 0.5)
case .MediumRight:
return CGPoint.init(x: 1, y: 0.5)
}
}
}
class SPGradientWithPictureView: SPGradientView {
var pictureView: UIView? {
willSet {
if self.pictureView != nil {
if self.subviews.contains(self.pictureView!) {
self.pictureView?.removeFromSuperview()
}
}
}
didSet {
if self.pictureView != nil {
self.addSubview(pictureView!)
var point: CGPoint {
switch self {
case .TopLeft:
return CGPoint.init(x: 0, y: 0)
case .TopCenter:
return CGPoint.init(x: 0.5, y: 0)
case .TopRight:
return CGPoint.init(x: 1, y: 0)
case .BottomLeft:
return CGPoint.init(x: 0, y: 1)
case .BottomCenter:
return CGPoint.init(x: 0.5, y: 1)
case .BottomRight:
return CGPoint.init(x: 1, y: 1)
case .MediumLeft:
return CGPoint.init(x: 0, y: 0.5)
case .MediumRight:
return CGPoint.init(x: 1, y: 0.5)
}
}
}
override func layoutSubviews() {
self.pictureView?.frame = self.bounds
}
}
@@ -21,23 +21,26 @@
import UIKit
public class SPCollectionContainerCell<ContentView: UIView>: UICollectionViewCell {
class SPSeparatorView: SPView {
let view = ContentView.init()
var currentIndexPath: IndexPath?
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.addSubview(view)
private var height: CGFloat {
return 0.5
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
override func commonInit() {
super.commonInit()
self.backgroundColor = UIColor.init(hex: "515B66").withAlphaComponent(0.4)
self.round = true
self.setHeight(self.height)
}
override public func layoutSubviews() {
func layout(origin: CGPoint, width: CGFloat) {
self.frame.origin = origin
self.set(width: width, height: self.height)
}
override func layoutSubviews() {
super.layoutSubviews()
self.view.setEqualsFrameFromBounds(self)
self.setHeight(self.height)
}
}
@@ -37,7 +37,7 @@ class SPEmptyProposeLabel: UILabel {
private func commonInit() {
self.setCenteringAlignment()
self.font = UIFont.system(type: .Regular, size: 14)
self.textColor = SPNativeStyleKit.Colors.gray
self.textColor = SPNativeColors.gray
}
required init?(coder aDecoder: NSCoder) {
@@ -54,7 +54,7 @@ class SPEmptyProposeView: UIView {
self.label.numberOfLines = 0
self.label.setCenteringAlignment()
self.label.font = UIFont.system(type: .Regular, size: 13)
self.label.textColor = SPNativeStyleKit.Colors.gray
self.label.textColor = SPNativeColors.gray
self.addSubview(self.label)
self.addSubview(self.button)
@@ -250,7 +250,7 @@ class SPBaseContentTableViewCell: SPTableViewCell {
self.descriptionLabel.numberOfLines = 0
self.descriptionLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 15)
self.descriptionLabel.textAlignment = .left
self.descriptionLabel.textColor = SPNativeStyleKit.Colors.gray
self.descriptionLabel.textColor = SPNativeColors.gray
self.contentView.addSubview(self.descriptionLabel)
self.descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
@@ -305,7 +305,7 @@ class SPBaseContentTableViewCell: SPTableViewCell {
self.descriptionLabel.text = "Description"
self.descriptionLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 15)
self.descriptionLabel.textAlignment = .left
self.descriptionLabel.textColor = SPNativeStyleKit.Colors.gray
self.descriptionLabel.textColor = SPNativeColors.gray
self.button.setTitle("Button")
self.imageSide = 63
@@ -1,155 +0,0 @@
// The MIT License (MIT)
// Copyright © 2017 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
class SPCollectionImagesTableViewCell: SPTableViewCell {
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let collectionView = SPImagesLineCollectionView()
var topSpace: CGFloat = 11 {
didSet {
self.titleLabelTopConstraint.constant = self.topSpace
self.updateConstraints()
}
}
var collectionHeight: CGFloat = 63 {
didSet {
self.collectionHeightConstraint.constant = self.collectionHeight
self.updateConstraints()
}
}
//constraints
private var collectionTopConstraint: NSLayoutConstraint!
private var collectionHeightConstraint: NSLayoutConstraint!
private var titleLabelTopConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
let marginGuide = contentView.layoutMarginsGuide
self.titleLabel.numberOfLines = 0
self.titleLabel.font = UIFont.system(type: UIFont.BoldType.Medium, size: 21)
self.titleLabel.textAlignment = .left
self.titleLabel.textColor = UIColor.black
self.contentView.addSubview(self.titleLabel)
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.titleLabel.leadingAnchor.constraint(equalTo:
marginGuide.leadingAnchor, constant: 0).isActive = true
self.titleLabel.trailingAnchor.constraint(equalTo:
marginGuide.trailingAnchor).isActive = true
self.titleLabelTopConstraint = self.titleLabel.topAnchor.constraint(equalTo:
marginGuide.topAnchor, constant: 0)
self.titleLabelTopConstraint.isActive = true
self.subtitleLabel.numberOfLines = 3
self.subtitleLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 15)
self.subtitleLabel.textAlignment = .left
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
self.contentView.addSubview(self.subtitleLabel)
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
self.subtitleLabel.leadingAnchor.constraint(equalTo:
self.titleLabel.leadingAnchor).isActive = true
self.subtitleLabel.trailingAnchor.constraint(equalTo:
marginGuide.trailingAnchor).isActive = true
self.subtitleLabel.topAnchor.constraint(equalTo:
titleLabel.bottomAnchor, constant: 3).isActive = true
self.collectionView.backgroundColor = UIColor.clear
self.collectionView.showsHorizontalScrollIndicator = false
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.collectionView)
self.collectionView.leadingAnchor.constraint(equalTo:
self.leadingAnchor).isActive = true
self.collectionView.trailingAnchor.constraint(equalTo:
self.trailingAnchor).isActive = true
self.collectionHeightConstraint = self.collectionView.heightAnchor.constraint(equalToConstant: 63)
self.collectionHeightConstraint.isActive = true
self.collectionTopConstraint = self.collectionView.topAnchor.constraint(equalTo:
subtitleLabel.bottomAnchor, constant: 10)
self.collectionTopConstraint.isActive = true
self.collectionView.bottomAnchor.constraint(equalTo:
marginGuide.bottomAnchor, constant: -7).isActive = true
}
override func prepareForReuse() {
super.prepareForReuse()
self.collectionView.reloadData()
self.accessoryType = .none
}
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.contentInset.left = self.contentView.layoutMargins.left
self.collectionView.contentInset.right = self.contentView.layoutMargins.right
self.accessoryView?.center.y = self.titleLabel.frame.bottomYPosition + self.subtitleLabel.frame.height / 2
if self.accessoryView?.frame.origin.y ?? 0 < 5 {
self.accessoryView?.center.y = self.frame.height / 2
}
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
var backgroundColor = SPNativeStyleKit.Colors.customGray
if let cell = self.collectionView.cellForItem(at: IndexPath.init(row: 0, section: 0)) as? SPImageCollectionViewCell {
if let color = cell.view.gradeView.backgroundColor {
backgroundColor = color
}
}
super.setHighlighted(highlighted, animated: animated)
for cell in self.collectionView.visibleCells {
if let imageCell = cell as? SPImageCollectionViewCell {
imageCell.view.gradeView.backgroundColor = backgroundColor
}
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
var backgroundColor = SPNativeStyleKit.Colors.customGray
if let cell = self.collectionView.cellForItem(at: IndexPath.init(row: 0, section: 0)) as? SPImageCollectionViewCell {
if let color = cell.view.gradeView.backgroundColor {
backgroundColor = color
}
}
super.setSelected(selected, animated: animated)
for cell in self.collectionView.visibleCells {
if let imageCell = cell as? SPImageCollectionViewCell {
imageCell.view.gradeView.backgroundColor = backgroundColor
}
}
}
}
@@ -79,7 +79,7 @@ class SPImageTableViewCell: SPTableViewCell {
self.subtitleLabel.numberOfLines = 3
self.subtitleLabel.font = UIFont.system(type: UIFont.BoldType.Regular, size: 15)
self.subtitleLabel.textAlignment = .left
self.subtitleLabel.textColor = SPNativeStyleKit.Colors.gray
self.subtitleLabel.textColor = SPNativeColors.gray
self.contentView.addSubview(self.subtitleLabel)
self.subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
self.subtitleLabel.leadingAnchor.constraint(equalTo:
@@ -37,7 +37,7 @@ class SPProposeTableViewCell: UITableViewCell {
self.button.titleLabel?.font = UIFont.system(type: .DemiBold, size: 14)
self.button.titleLabel?.numberOfLines = 0
self.button.secondColor = SPNativeStyleKit.Colors.blue
self.button.secondColor = SPNativeColors.blue
self.button.baseColor = UIColor.white
self.button.style = .base
self.button.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.center
@@ -45,7 +45,7 @@ class SPFormButtonTableViewCell: UITableViewCell {
self.backgroundColor = UIColor.white
self.button.setTitle("Button", for: .normal)
self.button.backgroundColor = UIColor.clear
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
self.button.setTitleColor(SPNativeColors.blue)
self.button.titleLabel?.font = UIFont.system(type: .Medium, size: 17)
self.selectionStyle = .none
self.contentView.addSubview(self.button)
@@ -34,7 +34,7 @@ class SPFormFeaturedTitleTableViewCell: UITableViewCell {
self.titleLabel.textColor = UIColor.black
case .smallColorful:
self.titleLabel.font = UIFont.system(type: .DemiBold, size: 11)
self.titleLabel.textColor = SPNativeStyleKit.Colors.blue
self.titleLabel.textColor = SPNativeColors.blue
self.titleLabel.text = self.titleLabel.text?.uppercased()
}
}
@@ -82,7 +82,7 @@ class SPFormFeaturedTitleTableViewCell: UITableViewCell {
self.withButton = false
self.button.setTitle("Button Title", for: UIControl.State.normal)
self.button.setTitleColor(SPNativeStyleKit.Colors.blue)
self.button.setTitleColor(SPNativeColors.blue)
self.button.titleLabel?.font = UIFont.system(type: .Medium, size: 17)
self.button.translatesAutoresizingMaskIntoConstraints = false
self.button.titleLabel?.textAlignment = .right
@@ -65,7 +65,7 @@ class SPFormLabelTableViewCell: SPTableViewCell {
self.descriptionLabel.text = "Description"
self.descriptionLabel.numberOfLines = 1
self.descriptionLabel.font = UIFont.system(type: .Regular, size: 17)
self.descriptionLabel.textColor = SPNativeStyleKit.Colors.gray
self.descriptionLabel.textColor = SPNativeColors.gray
self.contentView.addSubview(self.descriptionLabel)
self.stopLoading(animated: false)
@@ -21,30 +21,14 @@
import UIKit
class SPVibration {
struct SPVibration {
private let generator = UINotificationFeedbackGenerator()
func prepare() {
self.generator.prepare()
}
func impact(_ mode: Mode) {
switch mode {
case .success:
self.generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success)
case .warning:
self.generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.warning)
case .error:
self.generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.error)
}
}
private init() {}
}
extension SPVibration {
static func impact(_ mode: Mode) {
static func impact(system mode: SystemMode) {
let generator = UINotificationFeedbackGenerator()
switch mode {
@@ -72,7 +56,7 @@ extension SPVibration {
}
}
enum Mode {
enum SystemMode {
case error
case success
case warning
+63 -35
View File
@@ -1,24 +1,24 @@
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/90c836ec5649e77fb44ff727d7dad96d2009f3d8/Resources/SPStorkController - Name.svg"/>
<img src="https://github.com/IvanVorobei/SPStorkController/blob/master/Resources/Banner.svg"/>
Modal controller as in mail or Apple music application. Similar animation and transition. I tried to repeat all the animations, corner radius and frames. The controller supports gestures and Navigation Bar & work with ScrollView. You can see [how use pod tutorial](https://youtu.be/wOTNGswT2-0) on youtube
Modal controller like in Mail or Apple Music application. Similar animation and transition. I tried to recreate all the animations, corner radius and frames. Controller supports gestures and Navigation Bar and works with ScrollView. You can watch [how to use pod tutorial](https://youtu.be/wOTNGswT2-0) on YouTube.
Preview GIF loading `3mb`. Please, wait
Preview GIF is loading `3mb`. Please, wait.
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/c66764082c0d9bf11d0bd46d5fa458edb62044fe/Resources/gif-mockup - 3.gif" width="500">
<img src="https://github.com/IvanVorobei/SPStorkController/blob/master/Resources/Preview.gif" width="500">
You can download example [in AppStore](https://itunes.apple.com/app/id1446635818). If you want buy code of app on gif, please, [contact me](#contact). Source code can be used for educational purposes only
You can download example [from AppStore](https://itunes.apple.com/app/id1446635818). If you want to buy source code of the app shown on the GIF above, please go to [xcode-shop.com](https://xcode-shop.com). Price: $200.
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/4b1c91dacc4d3f901fcab5c7efdff256a40c4381/Resources/SPStorkController - Donate.svg"/>
<img src="https://github.com/IvanVorobei/SPStorkController/blob/master/Resources/Shop.svg"/>
The project is absolutely free, but but it takes time to support and update it. Your support is very motivating and very important. I often receive emails asking me to update or add functionality. [Small donate](https://money.yandex.ru/to/410012745748312) for a cup of coffee helps to develop the project and make it better
I have a store where I sell applications and modules for Xcode projects. You can find source codes of applications or custom animations / UI. I regularly update the code. Visit my website to see all items for sale: [xcode-shop.com](https://xcode-shop.com). On the website you can find previews and for some items links to AppStore.
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/4b1c91dacc4d3f901fcab5c7efdff256a40c4381/Resources/SPStorkController - Donate.svg"/>
<img src="https://github.com/IvanVorobei/SPStorkController/blob/master/Resources/Shop.svg"/>
## Requirements
Swift 4.2. Ready for use on iOS 10+
## Integration
Drop in `Source/SPStorkController` folder to your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`.
Put `Source/SPStorkController` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`.
Or via CocoaPods:
```ruby
@@ -46,17 +46,25 @@ class ViewController: UIViewController {
}
```
Please, _not_ init `SPStorkTransitioningDelegate` like here:
Please, do not init `SPStorkTransitioningDelegate` like this:
```swift
controller.transitioningDelegate = SPStorkTransitioningDelegate()
```
You will get error about weak property.
You will get an error about weak property.
### Video Tutorial
You can see how to use `SPStorkController` and how to customize it [in this video](https://youtu.be/wOTNGswT2-0). For English speakers Ive added subtitles, dont forget to turn them on:
[![Tutorial on YouTube](https://github.com/IvanVorobei/SPStorkController/blob/master/Resources/YouTube.jpg)](https://youtu.be/wOTNGswT2-0)
On my [YouTube channel](http://youtube.com/ivanvorobei) you can find videos about Xcode and Design. I would appreciate it if you like and subscribe. If you do not want to watch the video, I wrote a small wiki below.
### Light StatusBar
For set `light` status bar for presented controller, user `preferredStatusBarStyle` propert. Also set `modalPresentationCapturesStatusBarAppearance`. See example:
To set `light` status bar for presented controller, use `preferredStatusBarStyle` property. Also set `modalPresentationCapturesStatusBarAppearance`. See example:
```swift
import UIKit
@@ -74,32 +82,42 @@ class ModalViewController: UIViewController {
}
```
### Parametrs
### Parameters
- Parameter `isSwipeToDismissEnabled` enables dissmiss by swipe gester. Defualt is `true`:
- Parameter `isSwipeToDismissEnabled` enables dismissal by swipe gesture. Default is `true`:
```swift
transitionDelegate.isSwipeToDismissEnabled = true
```
- Parameter `isTapAroundToDismissEnabled` enables dissmiss by tap parrent controller. Defualt is `true`:
- Parameter `isTapAroundToDismissEnabled` enables dismissal by tapping parent controller. Default is `true`:
```swift
transitionDelegate.isTapAroundToDismissEnabled = true
```
- Parameter `showIndicator` shows or hides top arrow indicator. Defualt is `true`:
- Parameter `showIndicator` shows or hides top arrow indicator. Default is `true`:
```swift
transitionDelegate.showIndicator = true
```
- Parameter `customHeight` sets custom height for modal controller. Defualt is `nil`:
- Parameter `customHeight` sets custom height for modal controller. Default is `nil`:
```swift
transitionDelegate.customHeight = 350
```
### Snapshots
The project uses a snapshot of the screen in order to avoid compatibility and customization issues. Before controller presentation, a snapshot of the parent view is made, and size and position are changed for the snapshot. Sometimes you will need to update the screenshot of the parent view, for that use static func:
```swift
SPStorkController.updatePresentingController(modal: controller)
```
and pass the controller, which is modal and uses `SPStorkTransitioningDelegate`
### Add Navigation Bar
You may want to add a navigation bar to your modal controller. Since it became impossible to change or customize the native controller in swift 4 (I couldnt even find a way to change the height of bar), I had to completely create navigation bar. Visually, it looks real, but it doesnt execute the necessary functions:
You may want to add a navigation bar to your modal controller. Since it became impossible to change or customize the native controller in swift 4 (I couldnt even find a way to change the height of the bar), I had to recreate navigation bar from the ground up. Visually it looks real, but it doesnt execute the necessary functions:
```swift
import UIKit
@@ -122,19 +140,19 @@ class ModalController: UIViewController {
}
```
You only need to add a navigation bar to the main view, it will automatically layout. Use style `.stork` in init `SPFakeBarView`. It is image preview with Navigation Bar and without it:
You only need to add a navigation bar to the main view, it will automatically layout. Use style `.stork` in init of `SPFakeBarView`. Here is visual preview with Navigation Bar and without it:
<img src="https://rawcdn.githack.com/IvanVorobei/SPStorkController/916cfef888b3e70ca45d1b8b26fba1947421632b/Recources/SPStorkController - Banner.jpg"/>
<img src="https://github.com/IvanVorobei/SPStorkController/blob/master/Resources/Navigation%20Bar.jpg"/>
For use `SPFakeBarView` you should install [SparrowKit](https://github.com/IvanVorobei/SparrowKit) pod:
To use `SPFakeBarView` you need to install [SparrowKit](https://github.com/IvanVorobei/SparrowKit) pod:
```ruby
pod 'SparrowKit'
```
### Work with UIScrollView
### Working with UIScrollView
If you use `UIScrollView` (or UITableView & UICollectionView) on your controller, I recommend making it more interactive. When the scroll reaches the top position, the controller will interactively drag down, simulating a closing animation. To do this, set the delegate and in the function `scrollViewDidScroll` call:
If you use `UIScrollView` (or UITableView & UICollectionView) on your controller, I recommend making it more interactive. When scrolling reaches the top position, the controller will interactively drag down, simulating a closing animation. To do this, set the delegate and in the function `scrollViewDidScroll` call:
```swift
func scrollViewDidScroll(_ scrollView: UIScrollView) {
@@ -142,41 +160,51 @@ func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
```
### Work with UITableView & UICollectionView
### Working with UITableView & UICollectionView
Working with a collections classes is not difficult. In the `Example` folder you can find the implementation, however I will give a couple of tips to help the table look better.
Working with a collections classes is not difficult. In the `Example` folder you can find an implementation. However, I will give a couple of tips for making the table look better.
For fist, if you use `SPFakeBarView`, don't forget set top insets for content & scroll indicator. Also I am recomeded set bottom insets:
Firstly, if you use `SPFakeBarView`, don't forget to set top insets for content & scroll indicator. Also, I recommend setting bottom insets:
```swift
tableView.contentInset.top = self.navBar.height
tableView.scrollIndicatorInsets.top = self.navBar.height
tableView.contentInset.bottom = self.safeArea.bottom
tableView.scrollIndicatorInsets.bottom = self.safeArea.bottom
tableView.contentInset.bottom = self.safeAreaInsets.bottom
tableView.scrollIndicatorInsets.bottom = self.safeAreaInsets.bottom
```
Please, use also `SPStorkController.scrollViewDidScroll()` function in delegate for more intaractive with your collection or table view
Please, also use `SPStorkController.scrollViewDidScroll()` function in delegate for more interactiveness with your collection or table view
### Modal presentation of different controller
If you want to present modal controller on SPStorkController, please set:
```swift
controller.modalPresentationStyle = .custom
```
Its needed for correct presentation and dismissal of all modal controllers.
## My projects
Here I would like to offer you my other projects.
### SPPermission
Project [SPPermission](https://github.com/IvanVorobei/SPPermission) for managing permissions with the customizable visual effects. Beautiful dialog increases the chance of approval (which is important when we request notification). Simple control of this module saves you hours of development. You can start using project with just two lines of code and easy customization!
Project [SPPermission](https://github.com/IvanVorobei/SPPermission) for managing permissions with customizable visual effects. Beautiful dialog increases the chance of approval (which is important when we request notification). Simple control of this module saves you hours of development. You can start using project with just two lines of code and easy customization!
<img src="https://rawcdn.githack.com/IvanVorobei/RequestPermission/fb53d20f152a3e76e053e6af529306611fb794f0/resources/request-permission - mockup_preview.gif" width="500">
<img src="https://github.com/IvanVorobei/SPPermission/blob/master/Resources/Preview.gif" width="500">
### SparrowKit
The `SPStorkController` in the past was part of [SparrowKit](https://github.com/IvanVorobei/SparrowKit) library. In library you can find many useful extensions & classes. For install via CocoaPods use:
`SPStorkController` was formerly a part of [SparrowKit](https://github.com/IvanVorobei/SparrowKit) library. In the library you can find many useful extensions & classes. To install via CocoaPods use:
```ruby
pod 'SparrowKit'
```
## License
`SPStorkController` is released under the MIT license. Check `LICENSE.md` for details
`SPStorkController` is released under the MIT license. Check `LICENSE.md` for details.
## Contact
If you have any requirements to develop any application or UI, write to me at hello@ivanvorobei.by. I am developing iOS apps and creates designs too. I use `swift` for developing projects. For requesting more functionality, you should create a new issue.
Here are my apps in AppStore: [first account](https://itunes.apple.com/us/developer/polina-zubarik/id1434528595) & [second account](https://itunes.apple.com/us/developer/mikalai-varabei/id1435792103)
If you need any application or UI to be developed, message me at hello@ivanvorobei.by. I develop iOS apps and create designs, too. I use `swift` for development. To request more functionality, you should create a new issue.
Here are my apps in AppStore: [first account](https://itunes.apple.com/us/developer/polina-zubarik/id1434528595) & [second account](https://itunes.apple.com/us/developer/mikalai-varabei/id1435792103).
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SPStorkController"
s.version = "1.2"
s.version = "1.2.1"
s.summary = "Modal controller as mail or Apple music application"
s.homepage = "https://github.com/IvanVorobei/SPStorkController"
s.source = { :git => "https://github.com/IvanVorobei/SPStorkController.git", :tag => s.version }
@@ -65,7 +65,13 @@ class SPStorkPresentationController: UIPresentationController, UIGestureRecogniz
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else { return .zero }
let additionTranslate = containerView.bounds.height - (self.customHeight ?? containerView.bounds.height)
var customHeight = self.customHeight ?? containerView.bounds.height
if customHeight > containerView.bounds.height {
customHeight = containerView.bounds.height
print("SPStorkController - Custom height change to default value. Your height more maximum value")
}
let additionTranslate = containerView.bounds.height - customHeight
let yOffset: CGFloat = self.topSpace + 13 + additionTranslate
return CGRect(x: 0, y: yOffset, width: containerView.bounds.width, height: containerView.bounds.height - yOffset)
}