Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a14634869 | |||
| 42f03ef1be | |||
| 4e56501f41 | |||
| 6236c59b73 | |||
| e140d52e21 | |||
| 9c3ba97d6b | |||
| 892a66c9b0 | |||
| 01997fcddd | |||
| 49b0b16761 | |||
| 09c40f710e | |||
| f844311ff1 | |||
| 8669d9685c | |||
| 9c66a0b6f3 |
@@ -7,9 +7,9 @@
|
||||
#
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ContainerControllerSwift'
|
||||
s.version = '1.0.0'
|
||||
s.summary = 'This is a swipe-panel from application: https://www.apple.com/ios/maps/'
|
||||
s.name = 'ContainerControllerSwift'
|
||||
s.version = '1.0.5'
|
||||
s.summary = 'This is a swipe-panel from application: https://www.apple.com/ios/maps/'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
@@ -17,24 +17,26 @@ Pod::Spec.new do |s|
|
||||
# * Write the description between the DESC delimiters below.
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
|
||||
s.description = <<-DESC
|
||||
s.description = <<-DESC
|
||||
TODO: Add long description of the pod here.
|
||||
'This is a swipe-panel from application: https://www.apple.com/ios/maps/'
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/mrustaa/ContainerController'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'rustamburger@gmail.com' => 'rustamburger@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/mrustaa/ContainerController.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
s.homepage = 'https://github.com/mrustaa/ContainerController'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'mrustaa' => 'rustamburger@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/mrustaa/ContainerController.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
|
||||
s.swift_version = '5.2.4'
|
||||
s.ios.deployment_target = '13.0'
|
||||
# s.ios.deployment_target = '13.0'
|
||||
s.platform = :ios, "13.0"
|
||||
|
||||
s.source_files = 'ContainerControllerSwift/*.{swift}'
|
||||
s.source_files = 'ContainerControllerSwift/**/*.{swift}'
|
||||
|
||||
s.source_files = 'ContainerControllerSwift/*.{swift}'
|
||||
s.source_files = 'ContainerControllerSwift/ContainerTable/*.{swift}'
|
||||
s.source_files = 'ContainerControllerSwift/ContainerCollection/*.{swift}'
|
||||
s.framework = "UIKit"
|
||||
# s.ios.framework = 'UIKit'
|
||||
# s.resource_bundles = {
|
||||
# 'ContainerControllerSwift' => ['ContainerControllerSwift/Assets/*.png']
|
||||
# }
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# Be sure to run `pod lib lint ContainerControllerSwift.podspec' to ensure this is a
|
||||
# valid spec before submitting.
|
||||
#
|
||||
# Any lines starting with a # are optional, but their use is encouraged
|
||||
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
|
||||
#
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ContainerControllerSwift'
|
||||
s.version = '1.0.2'
|
||||
s.summary = 'This is a swipe-panel from application: https://www.apple.com/ios/maps/'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
# * Try to keep it short, snappy and to the point.
|
||||
# * Write the description between the DESC delimiters below.
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
|
||||
s.description = <<-DESC
|
||||
TODO: Add long description of the pod here.
|
||||
'This is a swipe-panel from application: https://www.apple.com/ios/maps/'
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/mrustaa/ContainerController'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'rustamburger@gmail.com' => 'rustamburger@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/mrustaa/ContainerController.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
|
||||
s.swift_version = '5.2.4'
|
||||
s.ios.deployment_target = '13.0'
|
||||
|
||||
s.source_files = 'ContainerControllerSwift/*.{swift}'
|
||||
s.source_files = 'ContainerControllerSwift/**/*.{swift}'
|
||||
|
||||
# s.resource_bundles = {
|
||||
# 'ContainerControllerSwift' => ['ContainerControllerSwift/Assets/*.png']
|
||||
# }
|
||||
|
||||
# s.public_header_files = 'Pod/Classes/**/*.h'
|
||||
# s.frameworks = 'UIKit', 'MapKit'
|
||||
# s.dependency 'AFNetworking', '~> 2.3'
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// CollectionAdapterTypes.swift
|
||||
// PatternsSwift
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 01/05/2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
typealias CollectionAdapterCountCallback = () -> Int
|
||||
typealias CollectionAdapterCellIndexCallback = (_ index: Int) -> UICollectionViewCell
|
||||
typealias CollectionAdapterSizeIndexCallback = (_ index: Int) -> CGSize
|
||||
typealias CollectionAdapterSelectIndexCallback = (_ index: Int) -> ()
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// ContainerTypes.swift
|
||||
// PatternsSwift
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 21/04/2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
typealias TableAdapterCountCallback = () -> Int
|
||||
typealias TableAdapterCellIndexCallback = (_ index: Int) -> UITableViewCell
|
||||
typealias TableAdapterHeightIndexCallback = (_ index: Int) -> CGFloat
|
||||
typealias TableAdapterSelectIndexCallback = (_ index: Int) -> ()
|
||||
typealias TableAdapterDidScrollCallback = () -> ()
|
||||
typealias TableAdapterDeleteIndexCallback = (_ index: Int) -> ()
|
||||
+4
-4
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class CollectionAdapterCell: UICollectionViewCell {
|
||||
open class CollectionAdapterCell: UICollectionViewCell {
|
||||
|
||||
@IBInspectable var hideAnimation: Bool = false
|
||||
var selectedView: UIView?
|
||||
@@ -24,7 +24,7 @@ class CollectionAdapterCell: UICollectionViewCell {
|
||||
setupCommonProperties()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupCommonProperties()
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class CollectionAdapterCell: UICollectionViewCell {
|
||||
let selAlpha: CGFloat = 0.2 // 0.15
|
||||
|
||||
|
||||
override var isSelected: Bool {
|
||||
open override var isSelected: Bool {
|
||||
set {
|
||||
super.isSelected = newValue
|
||||
|
||||
@@ -85,7 +85,7 @@ class CollectionAdapterCell: UICollectionViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
open override var isHighlighted: Bool {
|
||||
set {
|
||||
super.isHighlighted = newValue
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class CollectionAdapterCellData: NSObject {
|
||||
open class CollectionAdapterCellData: NSObject {
|
||||
|
||||
public var selectCallback: (() -> Void)?
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class CollectionAdapterItem: NSObject {
|
||||
open class CollectionAdapterItem: NSObject {
|
||||
|
||||
public let cellClass: AnyClass
|
||||
public let cellData: CollectionAdapterCellData?
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// CollectionAdapterTypes.swift
|
||||
// PatternsSwift
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 01/05/2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public typealias CollectionAdapterCountCallback = () -> Int
|
||||
public typealias CollectionAdapterCellIndexCallback = (_ index: Int) -> UICollectionViewCell
|
||||
public typealias CollectionAdapterSizeIndexCallback = (_ index: Int) -> CGSize
|
||||
public typealias CollectionAdapterSelectIndexCallback = (_ index: Int) -> ()
|
||||
+5
-5
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class CollectionAdapterView: UICollectionView {
|
||||
open class CollectionAdapterView: UICollectionView {
|
||||
|
||||
var countCallback: CollectionAdapterCountCallback?
|
||||
var cellIndexCallback: CollectionAdapterCellIndexCallback?
|
||||
@@ -17,7 +17,7 @@ class CollectionAdapterView: UICollectionView {
|
||||
|
||||
var items: [CollectionAdapterItem] = []
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
update()
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class CollectionAdapterView: UICollectionView {
|
||||
|
||||
extension CollectionAdapterView: UICollectionViewDelegate {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
if !items.isEmpty {
|
||||
let item = items[indexPath.row]
|
||||
item.cellData?.selectCallback?()
|
||||
@@ -78,7 +78,7 @@ extension CollectionAdapterView: UICollectionViewDelegate {
|
||||
|
||||
extension CollectionAdapterView: UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
if !items.isEmpty {
|
||||
return items.count
|
||||
}
|
||||
@@ -88,7 +88,7 @@ extension CollectionAdapterView: UICollectionViewDataSource {
|
||||
return 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
if !items.isEmpty {
|
||||
let item = items[indexPath.row]
|
||||
let cell = cellAt(indexPath)
|
||||
+13
-13
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class ContainerController: NSObject {
|
||||
open class ContainerController: NSObject {
|
||||
|
||||
// MARK: Views
|
||||
|
||||
@@ -1094,7 +1094,7 @@ class ContainerController: NSObject {
|
||||
|
||||
extension ContainerController: UIGestureRecognizerDelegate {
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1104,14 +1104,14 @@ extension ContainerController: UIGestureRecognizerDelegate {
|
||||
|
||||
extension ContainerController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
if let tableAdapterView = scrollView as? TableAdapterView {
|
||||
return tableAdapterView.tableView(tableView, heightForRowAt: indexPath)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if let tableAdapterView = scrollView as? TableAdapterView {
|
||||
return tableAdapterView.tableView(tableView, didSelectRowAt: indexPath)
|
||||
}
|
||||
@@ -1123,14 +1123,14 @@ extension ContainerController: UITableViewDelegate {
|
||||
|
||||
extension ContainerController: UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if let tableAdapterView = scrollView as? TableAdapterView {
|
||||
return tableAdapterView.tableView(tableView, numberOfRowsInSection: section)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
if let tableAdapterView = scrollView as? TableAdapterView {
|
||||
return tableAdapterView.tableView(tableView, cellForRowAt: indexPath)
|
||||
}
|
||||
@@ -1143,7 +1143,7 @@ extension ContainerController: UITableViewDataSource {
|
||||
|
||||
extension ContainerController: UICollectionViewDelegate {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
if let collectionAdapterView = scrollView as? CollectionAdapterView {
|
||||
collectionAdapterView.collectionView(collectionView, didSelectItemAt: indexPath)
|
||||
}
|
||||
@@ -1155,14 +1155,14 @@ extension ContainerController: UICollectionViewDelegate {
|
||||
|
||||
extension ContainerController: UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
if let collectionAdapterView = scrollView as? CollectionAdapterView {
|
||||
return collectionAdapterView.collectionView(collectionView, numberOfItemsInSection: section)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
if let collectionAdapterView = scrollView as? CollectionAdapterView {
|
||||
return collectionAdapterView.collectionView(collectionView, cellForItemAt: indexPath)
|
||||
}
|
||||
@@ -1186,7 +1186,7 @@ extension ContainerController: UICollectionViewDelegateFlowLayout {
|
||||
|
||||
extension ContainerController: UIScrollViewDelegate {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
if let tableAdapterView = scrollView as? TableAdapterView {
|
||||
tableAdapterView.scrollViewDidScroll(tableAdapterView)
|
||||
@@ -1296,7 +1296,7 @@ extension ContainerController: UIScrollViewDelegate {
|
||||
|
||||
// MARK: - Scroll Begin/End Dragging
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
|
||||
isScrolling = true
|
||||
|
||||
@@ -1309,11 +1309,11 @@ extension ContainerController: UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
isScrolling = false
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
|
||||
if !decelerate {
|
||||
isScrolling = false
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ContainerControllerDelegate {
|
||||
public protocol ContainerControllerDelegate {
|
||||
|
||||
/// Reports rotation and orientation changes
|
||||
func containerControllerRotation(_ containerController: ContainerController)
|
||||
@@ -21,7 +21,7 @@ protocol ContainerControllerDelegate {
|
||||
|
||||
}
|
||||
|
||||
extension ContainerControllerDelegate {
|
||||
public extension ContainerControllerDelegate {
|
||||
|
||||
func containerControllerRotation(_ containerController: ContainerController) {
|
||||
}
|
||||
+2
-4
@@ -2,11 +2,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
extension ContainerDevice {
|
||||
public extension ContainerDevice {
|
||||
|
||||
public enum Orientation {
|
||||
enum Orientation {
|
||||
case portrait
|
||||
case landscapeLeft
|
||||
case landscapeRight
|
||||
+3
-3
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
// MARK: - Position
|
||||
|
||||
class ContainerPosition {
|
||||
open class ContainerPosition {
|
||||
|
||||
var top: CGFloat
|
||||
|
||||
@@ -30,7 +30,7 @@ class ContainerPosition {
|
||||
|
||||
// MARK: - Insets
|
||||
|
||||
struct ContainerInsets {
|
||||
public struct ContainerInsets {
|
||||
|
||||
var right: CGFloat
|
||||
|
||||
@@ -46,7 +46,7 @@ struct ContainerInsets {
|
||||
|
||||
// MARK: - Layout
|
||||
|
||||
class ContainerLayout {
|
||||
open class ContainerLayout {
|
||||
|
||||
/**
|
||||
Initialization start position.
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class TableAdapterCell: UITableViewCell {
|
||||
open class TableAdapterCell: UITableViewCell {
|
||||
|
||||
@IBInspectable var hideAnimation: Bool = false
|
||||
var selectedView: UIView?
|
||||
@@ -35,7 +35,7 @@ class TableAdapterCell: UITableViewCell {
|
||||
|
||||
let selAlpha: CGFloat = 0.2 // 0.15
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
open override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
|
||||
if hideAnimation {
|
||||
|
||||
@@ -65,7 +65,7 @@ class TableAdapterCell: UITableViewCell {
|
||||
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
open override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
|
||||
if hideAnimation {
|
||||
if highlighted {
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class TableAdapterCellData: NSObject {
|
||||
open class TableAdapterCellData: NSObject {
|
||||
|
||||
// public let cellIdentifier: String
|
||||
//
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class TableAdapterItem: NSObject {
|
||||
open class TableAdapterItem: NSObject {
|
||||
|
||||
public let cellClass: AnyClass
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ContainerTypes.swift
|
||||
// PatternsSwift
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 21/04/2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public typealias TableAdapterCountCallback = () -> Int
|
||||
public typealias TableAdapterCellIndexCallback = (_ index: Int) -> UITableViewCell
|
||||
public typealias TableAdapterHeightIndexCallback = (_ index: Int) -> CGFloat
|
||||
public typealias TableAdapterSelectIndexCallback = (_ index: Int) -> ()
|
||||
public typealias TableAdapterDidScrollCallback = () -> ()
|
||||
public typealias TableAdapterDeleteIndexCallback = (_ index: Int) -> ()
|
||||
+5
-5
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class TableAdapterView: UITableView {
|
||||
open class TableAdapterView: UITableView {
|
||||
|
||||
@IBInspectable var separatorClr: UIColor?
|
||||
|
||||
@@ -22,7 +22,7 @@ class TableAdapterView: UITableView {
|
||||
|
||||
var items: [TableAdapterItem] = []
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
update()
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class TableAdapterView: UITableView {
|
||||
update()
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
open override func draw(_ rect: CGRect) {
|
||||
if let color = separatorClr {
|
||||
separatorColor = color
|
||||
}
|
||||
@@ -122,7 +122,7 @@ extension TableAdapterView: UITableViewDataSource {
|
||||
}
|
||||
|
||||
|
||||
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
if !items.isEmpty {
|
||||
let item = items[indexPath.row]
|
||||
return item.canEditing()
|
||||
@@ -130,7 +130,7 @@ extension TableAdapterView: UITableViewDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
if editingStyle == .delete {
|
||||
|
||||
items.remove(at: indexPath.row)
|
||||
+1
-1
@@ -16,7 +16,7 @@ public enum ContainerMoveType {
|
||||
case custom
|
||||
}
|
||||
|
||||
enum ContainerFromType {
|
||||
public enum ContainerFromType {
|
||||
case pan
|
||||
case scroll
|
||||
case scrollBorder
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class ContainerView: UIView {
|
||||
open class ContainerView: UIView {
|
||||
|
||||
var contentView: UIView?
|
||||
|
||||
@@ -44,7 +44,7 @@ class ContainerView: UIView {
|
||||
self.contentView = contentView
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
@@ -189,7 +189,6 @@
|
||||
607FACE11AFB9204008FA782 /* Sources */,
|
||||
607FACE21AFB9204008FA782 /* Frameworks */,
|
||||
607FACE31AFB9204008FA782 /* Resources */,
|
||||
E34E2A83D00021FED54D587A /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -208,7 +207,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0830;
|
||||
LastUpgradeCheck = 0830;
|
||||
LastUpgradeCheck = 1150;
|
||||
ORGANIZATIONNAME = CocoaPods;
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
@@ -224,10 +223,9 @@
|
||||
};
|
||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ContainerControllerSwift" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
@@ -325,24 +323,6 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E34E2A83D00021FED54D587A /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-ContainerControllerSwift_Tests/Pods-ContainerControllerSwift_Tests-frameworks.sh",
|
||||
"${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSnapshotTestCase.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ContainerControllerSwift_Tests/Pods-ContainerControllerSwift_Tests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -397,6 +377,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -405,12 +386,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -443,6 +426,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -450,6 +434,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -458,12 +443,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -488,6 +475,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -503,7 +491,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -518,7 +506,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -539,7 +527,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ContainerControllerSwift_Example.app/ContainerControllerSwift_Example";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -557,7 +545,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ContainerControllerSwift_Example.app/ContainerControllerSwift_Example";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
+10
-16
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "1150"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,8 +40,16 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "ContainerControllerSwift_Example.app"
|
||||
BlueprintName = "ContainerControllerSwift_Example"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -54,23 +62,11 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "ContainerControllerSwift_Example.app"
|
||||
BlueprintName = "ContainerControllerSwift_Example"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
@@ -87,8 +83,6 @@
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,11 +1,10 @@
|
||||
use_frameworks!
|
||||
|
||||
target 'ContainerControllerSwift_Example' do
|
||||
pod 'ContainerControllerSwift', :path => '../'
|
||||
pod 'ContainerControllerSwift'
|
||||
|
||||
target 'ContainerControllerSwift_Tests' do
|
||||
inherit! :search_paths
|
||||
|
||||
pod 'FBSnapshotTestCase' , '~> 2.1.4'
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
+3
-14
@@ -1,27 +1,16 @@
|
||||
PODS:
|
||||
- ContainerControllerSwift (0.1.0)
|
||||
- FBSnapshotTestCase (2.1.4):
|
||||
- FBSnapshotTestCase/SwiftSupport (= 2.1.4)
|
||||
- FBSnapshotTestCase/Core (2.1.4)
|
||||
- FBSnapshotTestCase/SwiftSupport (2.1.4):
|
||||
- FBSnapshotTestCase/Core
|
||||
- ContainerControllerSwift (1.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- ContainerControllerSwift (from `../`)
|
||||
- FBSnapshotTestCase (~> 2.1.4)
|
||||
|
||||
SPEC REPOS:
|
||||
https://cdn.cocoapods.org/:
|
||||
- FBSnapshotTestCase
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
ContainerControllerSwift:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
ContainerControllerSwift: d9165e33edebeea0b8e0992717b0811328400d3c
|
||||
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
|
||||
ContainerControllerSwift: 784bd547f936a51b956788cdd23869344e638119
|
||||
|
||||
PODFILE CHECKSUM: 748541873723babe6decea03fbfa834df5db1b55
|
||||
PODFILE CHECKSUM: 5f9e7bdcf65f6b742444242add916867543ae8c0
|
||||
|
||||
COCOAPODS: 1.9.3
|
||||
|
||||
Generated
-20
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIApplication (StrictKeyWindow)
|
||||
|
||||
/**
|
||||
@return The receiver's @c keyWindow. Raises an assertion if @c nil.
|
||||
*/
|
||||
- (UIWindow *)fb_strictKeyWindow;
|
||||
|
||||
@end
|
||||
Generated
-27
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <FBSnapshotTestCase/UIApplication+StrictKeyWindow.h>
|
||||
|
||||
@implementation UIApplication (StrictKeyWindow)
|
||||
|
||||
- (UIWindow *)fb_strictKeyWindow
|
||||
{
|
||||
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
|
||||
if (!keyWindow) {
|
||||
[NSException raise:@"FBSnapshotTestCaseNilKeyWindowException"
|
||||
format:@"Snapshot tests must be hosted by an application with a key window. Please ensure your test"
|
||||
" host sets up a key window at launch (either via storyboards or programmatically) and doesn't"
|
||||
" do anything to remove it while snapshot tests are running."];
|
||||
}
|
||||
return keyWindow;
|
||||
}
|
||||
|
||||
@end
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// 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/UIKit.h>
|
||||
|
||||
@interface UIImage (Compare)
|
||||
|
||||
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance;
|
||||
|
||||
@end
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// 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 <FBSnapshotTestCase/UIImage+Compare.h>
|
||||
|
||||
// This makes debugging much more fun
|
||||
typedef union {
|
||||
uint32_t raw;
|
||||
unsigned char bytes[4];
|
||||
struct {
|
||||
char red;
|
||||
char green;
|
||||
char blue;
|
||||
char alpha;
|
||||
} __attribute__ ((packed)) pixels;
|
||||
} FBComparePixel;
|
||||
|
||||
@implementation UIImage (Compare)
|
||||
|
||||
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
|
||||
{
|
||||
NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");
|
||||
|
||||
CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage));
|
||||
CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
|
||||
|
||||
// The images have the equal size, so we could use the smallest amount of bytes because of byte padding
|
||||
size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage));
|
||||
size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow;
|
||||
void *referenceImagePixels = calloc(1, referenceImageSizeBytes);
|
||||
void *imagePixels = calloc(1, referenceImageSizeBytes);
|
||||
|
||||
if (!referenceImagePixels || !imagePixels) {
|
||||
free(referenceImagePixels);
|
||||
free(imagePixels);
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels,
|
||||
referenceImageSize.width,
|
||||
referenceImageSize.height,
|
||||
CGImageGetBitsPerComponent(self.CGImage),
|
||||
minBytesPerRow,
|
||||
CGImageGetColorSpace(self.CGImage),
|
||||
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
|
||||
);
|
||||
CGContextRef imageContext = CGBitmapContextCreate(imagePixels,
|
||||
imageSize.width,
|
||||
imageSize.height,
|
||||
CGImageGetBitsPerComponent(image.CGImage),
|
||||
minBytesPerRow,
|
||||
CGImageGetColorSpace(image.CGImage),
|
||||
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
|
||||
);
|
||||
|
||||
if (!referenceImageContext || !imageContext) {
|
||||
CGContextRelease(referenceImageContext);
|
||||
CGContextRelease(imageContext);
|
||||
free(referenceImagePixels);
|
||||
free(imagePixels);
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, referenceImageSize.width, referenceImageSize.height), self.CGImage);
|
||||
CGContextDrawImage(imageContext, CGRectMake(0, 0, imageSize.width, imageSize.height), image.CGImage);
|
||||
|
||||
CGContextRelease(referenceImageContext);
|
||||
CGContextRelease(imageContext);
|
||||
|
||||
BOOL imageEqual = YES;
|
||||
|
||||
// Do a fast compare if we can
|
||||
if (tolerance == 0) {
|
||||
imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
|
||||
} else {
|
||||
// Go through each pixel in turn and see if it is different
|
||||
const NSInteger pixelCount = referenceImageSize.width * referenceImageSize.height;
|
||||
|
||||
FBComparePixel *p1 = referenceImagePixels;
|
||||
FBComparePixel *p2 = imagePixels;
|
||||
|
||||
NSInteger numDiffPixels = 0;
|
||||
for (int n = 0; n < pixelCount; ++n) {
|
||||
// If this pixel is different, increment the pixel diff count and see
|
||||
// if we have hit our limit.
|
||||
if (p1->raw != p2->raw) {
|
||||
numDiffPixels ++;
|
||||
|
||||
CGFloat percent = (CGFloat)numDiffPixels / pixelCount;
|
||||
if (percent > tolerance) {
|
||||
imageEqual = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p1++;
|
||||
p2++;
|
||||
}
|
||||
}
|
||||
|
||||
free(referenceImagePixels);
|
||||
free(imagePixels);
|
||||
|
||||
return imageEqual;
|
||||
}
|
||||
|
||||
@end
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// 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/UIKit.h>
|
||||
|
||||
@interface UIImage (Diff)
|
||||
|
||||
- (UIImage *)fb_diffWithImage:(UIImage *)image;
|
||||
|
||||
@end
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// 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 <FBSnapshotTestCase/UIImage+Diff.h>
|
||||
|
||||
@implementation UIImage (Diff)
|
||||
|
||||
- (UIImage *)fb_diffWithImage:(UIImage *)image
|
||||
{
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height));
|
||||
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
|
||||
CGContextSetAlpha(context, 0.5);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
|
||||
CGContextSetBlendMode(context, kCGBlendModeDifference);
|
||||
CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height));
|
||||
CGContextEndTransparencyLayer(context);
|
||||
UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return returnImage;
|
||||
}
|
||||
|
||||
@end
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIImage (Snapshot)
|
||||
|
||||
/// Uses renderInContext: to get a snapshot of the layer.
|
||||
+ (UIImage *)fb_imageForLayer:(CALayer *)layer;
|
||||
|
||||
/// Uses renderInContext: to get a snapshot of the view layer.
|
||||
+ (UIImage *)fb_imageForViewLayer:(UIView *)view;
|
||||
|
||||
/// Uses drawViewHierarchyInRect: to get a snapshot of the view and adds the view into a window if needed.
|
||||
+ (UIImage *)fb_imageForView:(UIView *)view;
|
||||
|
||||
@end
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <FBSnapshotTestCase/UIImage+Snapshot.h>
|
||||
#import <FBSnapshotTestCase/UIApplication+StrictKeyWindow.h>
|
||||
|
||||
@implementation UIImage (Snapshot)
|
||||
|
||||
+ (UIImage *)fb_imageForLayer:(CALayer *)layer
|
||||
{
|
||||
CGRect bounds = layer.bounds;
|
||||
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
|
||||
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
NSAssert1(context, @"Could not generate context for layer %@", layer);
|
||||
CGContextSaveGState(context);
|
||||
[layer layoutIfNeeded];
|
||||
[layer renderInContext:context];
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
+ (UIImage *)fb_imageForViewLayer:(UIView *)view
|
||||
{
|
||||
[view layoutIfNeeded];
|
||||
return [self fb_imageForLayer:view.layer];
|
||||
}
|
||||
|
||||
+ (UIImage *)fb_imageForView:(UIView *)view
|
||||
{
|
||||
CGRect bounds = view.bounds;
|
||||
NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view);
|
||||
NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view);
|
||||
|
||||
// If the input view is already a UIWindow, then just use that. Otherwise wrap in a window.
|
||||
UIWindow *window = [view isKindOfClass:[UIWindow class]] ? (UIWindow *)view : view.window;
|
||||
BOOL removeFromSuperview = NO;
|
||||
if (!window) {
|
||||
window = [[UIApplication sharedApplication] fb_strictKeyWindow];
|
||||
}
|
||||
|
||||
if (!view.window && view != window) {
|
||||
[window addSubview:view];
|
||||
removeFromSuperview = YES;
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
|
||||
[view layoutIfNeeded];
|
||||
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
if (removeFromSuperview) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
/*
|
||||
There are three ways of setting reference image directories.
|
||||
|
||||
1. Set the preprocessor macro FB_REFERENCE_IMAGE_DIR to a double quoted
|
||||
c-string with the path.
|
||||
2. Set an environment variable named FB_REFERENCE_IMAGE_DIR with the path. This
|
||||
takes precedence over the preprocessor macro to allow for run-time override.
|
||||
3. Keep everything unset, which will cause the reference images to be looked up
|
||||
inside the bundle holding the current test, in the
|
||||
Resources/ReferenceImages_* directories.
|
||||
*/
|
||||
#ifndef FB_REFERENCE_IMAGE_DIR
|
||||
#define FB_REFERENCE_IMAGE_DIR ""
|
||||
#endif
|
||||
|
||||
/**
|
||||
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
|
||||
@param view The view to snapshot
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param suffixes An NSOrderedSet of strings for the different suffixes
|
||||
@param tolerance The percentage of pixels that can differ and still count as an 'identical' view
|
||||
*/
|
||||
#define FBSnapshotVerifyViewWithOptions(view__, identifier__, suffixes__, tolerance__) \
|
||||
FBSnapshotVerifyViewOrLayerWithOptions(View, view__, identifier__, suffixes__, tolerance__)
|
||||
|
||||
#define FBSnapshotVerifyView(view__, identifier__) \
|
||||
FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0)
|
||||
|
||||
|
||||
/**
|
||||
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
|
||||
@param layer The layer to snapshot
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param suffixes An NSOrderedSet of strings for the different suffixes
|
||||
@param tolerance The percentage of pixels that can differ and still count as an 'identical' layer
|
||||
*/
|
||||
#define FBSnapshotVerifyLayerWithOptions(layer__, identifier__, suffixes__, tolerance__) \
|
||||
FBSnapshotVerifyViewOrLayerWithOptions(Layer, layer__, identifier__, suffixes__, tolerance__)
|
||||
|
||||
#define FBSnapshotVerifyLayer(layer__, identifier__) \
|
||||
FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0)
|
||||
|
||||
|
||||
#define FBSnapshotVerifyViewOrLayerWithOptions(what__, viewOrLayer__, identifier__, suffixes__, tolerance__) \
|
||||
{ \
|
||||
NSString *errorDescription = [self snapshotVerifyViewOrLayer:viewOrLayer__ identifier:identifier__ suffixes:suffixes__ tolerance:tolerance__]; \
|
||||
BOOL noErrors = (errorDescription == nil); \
|
||||
XCTAssertTrue(noErrors, @"%@", errorDescription); \
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test
|
||||
and compare an image of the view to a reference image that write lots of complex layout-code tests.
|
||||
|
||||
In order to flip the tests in your subclass to record the reference images set @c recordMode to @c YES.
|
||||
|
||||
@attention When recording, the reference image directory should be explicitly
|
||||
set, otherwise the images may be written to somewhere inside the
|
||||
simulator directory.
|
||||
|
||||
For example:
|
||||
@code
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
self.recordMode = YES;
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
@interface FBSnapshotTestCase : XCTestCase
|
||||
|
||||
/**
|
||||
When YES, the test macros will save reference images, rather than performing an actual test.
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign) BOOL recordMode;
|
||||
|
||||
/**
|
||||
When @c YES appends the name of the device model and OS to the snapshot file name.
|
||||
The default value is @c NO.
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic;
|
||||
|
||||
/**
|
||||
When YES, renders a snapshot of the complete view hierarchy as visible onscreen.
|
||||
There are several things that do not work if renderInContext: is used.
|
||||
- UIVisualEffect #70
|
||||
- UIAppearance #91
|
||||
- Size Classes #92
|
||||
|
||||
@attention If the view does't belong to a UIWindow, it will create one and add the view as a subview.
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect;
|
||||
|
||||
- (void)setUp NS_REQUIRES_SUPER;
|
||||
- (void)tearDown NS_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
Performs the comparison or records a snapshot of the layer if recordMode is YES.
|
||||
@param viewOrLayer The UIView or CALayer to snapshot
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param suffixes An NSOrderedSet of strings for the different suffixes
|
||||
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
|
||||
@returns nil if the comparison (or saving of the reference image) succeeded. Otherwise it contains an error description.
|
||||
*/
|
||||
- (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer
|
||||
identifier:(NSString *)identifier
|
||||
suffixes:(NSOrderedSet *)suffixes
|
||||
tolerance:(CGFloat)tolerance;
|
||||
|
||||
/**
|
||||
Performs the comparison or records a snapshot of the layer if recordMode is YES.
|
||||
@param layer The Layer to snapshot
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
|
||||
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison or records a snapshot of the view if recordMode is YES.
|
||||
@param view The view to snapshot
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
|
||||
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Checks if reference image with identifier based name exists in the reference images directory.
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if reference image exists.
|
||||
*/
|
||||
- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Returns the reference image directory.
|
||||
|
||||
Helper function used to implement the assert macros.
|
||||
|
||||
@param dir directory to use if environment variable not specified. Ignored if null or empty.
|
||||
*/
|
||||
- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir;
|
||||
|
||||
@end
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestCase.h>
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
|
||||
|
||||
@implementation FBSnapshotTestCase
|
||||
{
|
||||
FBSnapshotTestController *_snapshotController;
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:NSStringFromClass([self class])];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
_snapshotController = nil;
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (BOOL)recordMode
|
||||
{
|
||||
return _snapshotController.recordMode;
|
||||
}
|
||||
|
||||
- (void)setRecordMode:(BOOL)recordMode
|
||||
{
|
||||
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
|
||||
_snapshotController.recordMode = recordMode;
|
||||
}
|
||||
|
||||
- (BOOL)isDeviceAgnostic
|
||||
{
|
||||
return _snapshotController.deviceAgnostic;
|
||||
}
|
||||
|
||||
- (void)setDeviceAgnostic:(BOOL)deviceAgnostic
|
||||
{
|
||||
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
|
||||
_snapshotController.deviceAgnostic = deviceAgnostic;
|
||||
}
|
||||
|
||||
- (BOOL)usesDrawViewHierarchyInRect
|
||||
{
|
||||
return _snapshotController.usesDrawViewHierarchyInRect;
|
||||
}
|
||||
|
||||
- (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect
|
||||
{
|
||||
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
|
||||
_snapshotController.usesDrawViewHierarchyInRect = usesDrawViewHierarchyInRect;
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer
|
||||
identifier:(NSString *)identifier
|
||||
suffixes:(NSOrderedSet *)suffixes
|
||||
tolerance:(CGFloat)tolerance
|
||||
{
|
||||
if (nil == viewOrLayer) {
|
||||
return @"Object to be snapshotted must not be nil";
|
||||
}
|
||||
NSString *referenceImageDirectory = [self getReferenceImageDirectoryWithDefault:(@ FB_REFERENCE_IMAGE_DIR)];
|
||||
if (referenceImageDirectory == nil) {
|
||||
return @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.";
|
||||
}
|
||||
if (suffixes.count == 0) {
|
||||
return [NSString stringWithFormat:@"Suffixes set cannot be empty %@", suffixes];
|
||||
}
|
||||
|
||||
BOOL testSuccess = NO;
|
||||
NSError *error = nil;
|
||||
NSMutableArray *errors = [NSMutableArray array];
|
||||
|
||||
if (self.recordMode) {
|
||||
NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffixes.firstObject];
|
||||
BOOL referenceImageSaved = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:(identifier) tolerance:tolerance error:&error];
|
||||
if (!referenceImageSaved) {
|
||||
[errors addObject:error];
|
||||
}
|
||||
} else {
|
||||
for (NSString *suffix in suffixes) {
|
||||
NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffix];
|
||||
BOOL referenceImageAvailable = [self referenceImageRecordedInDirectory:referenceImagesDirectory identifier:(identifier) error:&error];
|
||||
|
||||
if (referenceImageAvailable) {
|
||||
BOOL comparisonSuccess = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:identifier tolerance:tolerance error:&error];
|
||||
[errors removeAllObjects];
|
||||
if (comparisonSuccess) {
|
||||
testSuccess = YES;
|
||||
break;
|
||||
} else {
|
||||
[errors addObject:error];
|
||||
}
|
||||
} else {
|
||||
[errors addObject:error];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!testSuccess) {
|
||||
return [NSString stringWithFormat:@"Snapshot comparison failed: %@", errors.firstObject];
|
||||
}
|
||||
if (self.recordMode) {
|
||||
return @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self _compareSnapshotOfViewOrLayer:layer
|
||||
referenceImagesDirectory:referenceImagesDirectory
|
||||
identifier:identifier
|
||||
tolerance:tolerance
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self _compareSnapshotOfViewOrLayer:view
|
||||
referenceImagesDirectory:referenceImagesDirectory
|
||||
identifier:identifier
|
||||
tolerance:tolerance
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
|
||||
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
|
||||
UIImage *referenceImage = [_snapshotController referenceImageForSelector:self.invocation.selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
|
||||
return (referenceImage != nil);
|
||||
}
|
||||
|
||||
- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir
|
||||
{
|
||||
NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"];
|
||||
if (envReferenceImageDirectory) {
|
||||
return envReferenceImageDirectory;
|
||||
}
|
||||
if (dir && dir.length > 0) {
|
||||
return dir;
|
||||
}
|
||||
return [[NSBundle bundleForClass:self.class].resourcePath stringByAppendingPathComponent:@"ReferenceImages"];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private API
|
||||
|
||||
- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
|
||||
return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
|
||||
selector:self.invocation.selector
|
||||
identifier:identifier
|
||||
tolerance:tolerance
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
@end
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
Returns a Boolean value that indicates whether the snapshot test is running in 64Bit.
|
||||
This method is a convenience for creating the suffixes set based on the architecture
|
||||
that the test is running.
|
||||
|
||||
@returns @c YES if the test is running in 64bit, otherwise @c NO.
|
||||
*/
|
||||
BOOL FBSnapshotTestCaseIs64Bit(void);
|
||||
|
||||
/**
|
||||
Returns a default set of strings that is used to append a suffix based on the architectures.
|
||||
@warning Do not modify this function, you can create your own and use it with @c FBSnapshotVerifyViewWithOptions()
|
||||
|
||||
@returns An @c NSOrderedSet object containing strings that are appended to the reference images directory.
|
||||
*/
|
||||
NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void);
|
||||
|
||||
/**
|
||||
Returns a fully «normalized» file name.
|
||||
Strips punctuation and spaces and replaces them with @c _. Also appends the device model, running OS and screen size to the file name.
|
||||
|
||||
@returns An @c NSString object containing the passed @c fileName with the device model, OS and screen size appended at the end.
|
||||
*/
|
||||
NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
|
||||
#import <FBSnapshotTestCase/UIApplication+StrictKeyWindow.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
BOOL FBSnapshotTestCaseIs64Bit(void)
|
||||
{
|
||||
#if __LP64__
|
||||
return YES;
|
||||
#else
|
||||
return NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void)
|
||||
{
|
||||
NSMutableOrderedSet *suffixesSet = [[NSMutableOrderedSet alloc] init];
|
||||
[suffixesSet addObject:@"_32"];
|
||||
[suffixesSet addObject:@"_64"];
|
||||
if (FBSnapshotTestCaseIs64Bit()) {
|
||||
return [suffixesSet reversedOrderedSet];
|
||||
}
|
||||
return [suffixesSet copy];
|
||||
}
|
||||
|
||||
NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName)
|
||||
{
|
||||
UIDevice *device = [UIDevice currentDevice];
|
||||
UIWindow *keyWindow = [[UIApplication sharedApplication] fb_strictKeyWindow];
|
||||
CGSize screenSize = keyWindow.bounds.size;
|
||||
NSString *os = device.systemVersion;
|
||||
|
||||
fileName = [NSString stringWithFormat:@"%@_%@%@_%.0fx%.0f", fileName, device.model, os, screenSize.width, screenSize.height];
|
||||
|
||||
NSMutableCharacterSet *invalidCharacters = [NSMutableCharacterSet new];
|
||||
[invalidCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
[invalidCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
|
||||
NSArray *validComponents = [fileName componentsSeparatedByCharactersInSet:invalidCharacters];
|
||||
fileName = [validComponents componentsJoinedByString:@"_"];
|
||||
|
||||
return fileName;
|
||||
}
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, FBSnapshotTestControllerErrorCode) {
|
||||
FBSnapshotTestControllerErrorCodeUnknown,
|
||||
FBSnapshotTestControllerErrorCodeNeedsRecord,
|
||||
FBSnapshotTestControllerErrorCodePNGCreationFailed,
|
||||
FBSnapshotTestControllerErrorCodeImagesDifferentSizes,
|
||||
FBSnapshotTestControllerErrorCodeImagesDifferent,
|
||||
};
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController use this domain.
|
||||
*/
|
||||
extern NSString *const FBSnapshotTestControllerErrorDomain;
|
||||
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary.
|
||||
*/
|
||||
extern NSString *const FBReferenceImageFilePathKey;
|
||||
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary.
|
||||
*/
|
||||
extern NSString *const FBReferenceImageKey;
|
||||
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary.
|
||||
*/
|
||||
extern NSString *const FBCapturedImageKey;
|
||||
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary.
|
||||
*/
|
||||
extern NSString *const FBDiffedImageKey;
|
||||
|
||||
/**
|
||||
Provides the heavy-lifting for FBSnapshotTestCase. It loads and saves images, along with performing the actual pixel-
|
||||
by-pixel comparison of images.
|
||||
Instances are initialized with the test class, and directories to read and write to.
|
||||
*/
|
||||
@interface FBSnapshotTestController : NSObject
|
||||
|
||||
/**
|
||||
Record snapshots.
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign) BOOL recordMode;
|
||||
|
||||
/**
|
||||
When @c YES appends the name of the device model and OS to the snapshot file name.
|
||||
The default value is @c NO.
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic;
|
||||
|
||||
/**
|
||||
Uses drawViewHierarchyInRect:afterScreenUpdates: to draw the image instead of renderInContext:
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect;
|
||||
|
||||
/**
|
||||
The directory in which referfence images are stored.
|
||||
*/
|
||||
@property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory;
|
||||
|
||||
/**
|
||||
@param testClass The subclass of FBSnapshotTestCase that is using this controller.
|
||||
@returns An instance of FBSnapshotTestController.
|
||||
*/
|
||||
- (instancetype)initWithTestClass:(Class)testClass;
|
||||
|
||||
/**
|
||||
Designated initializer.
|
||||
@param testName The name of the tests.
|
||||
@returns An instance of FBSnapshotTestController.
|
||||
*/
|
||||
- (instancetype)initWithTestName:(NSString *)testName;
|
||||
|
||||
/**
|
||||
Performs the comparison of the layer.
|
||||
@param layer The Layer to snapshot.
|
||||
@param selector The test method being run.
|
||||
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison of the view.
|
||||
@param view The view to snapshot.
|
||||
@param selector The test method being run.
|
||||
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison of a view or layer.
|
||||
@param view The view or layer to snapshot.
|
||||
@param selector The test method being run.
|
||||
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
|
||||
@param tolerance The percentage of pixels that can differ and still be considered 'identical'
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Loads a reference image.
|
||||
@param selector The test method being run.
|
||||
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
|
||||
@param errorPtr An error, if this methods returns nil, the error will be something useful.
|
||||
@returns An image.
|
||||
*/
|
||||
- (UIImage *)referenceImageForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs a pixel-by-pixel comparison of the two images with an allowable margin of error.
|
||||
@param referenceImage The reference (correct) image.
|
||||
@param image The image to test against the reference.
|
||||
@param tolerance The percentage of pixels that can differ and still be considered 'identical'
|
||||
@param errorPtr An error that indicates why the comparison failed if it does.
|
||||
@returns YES if the comparison succeeded and the images are the same(ish).
|
||||
*/
|
||||
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
|
||||
toImage:(UIImage *)image
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Saves the reference image and the test image to `failedOutputDirectory`.
|
||||
@param referenceImage The reference (correct) image.
|
||||
@param testImage The image to test against the reference.
|
||||
@param selector The test method being run.
|
||||
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
|
||||
@param errorPtr An error that indicates why the comparison failed if it does.
|
||||
@returns YES if the save succeeded.
|
||||
*/
|
||||
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
|
||||
testImage:(UIImage *)testImage
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
@end
|
||||
-358
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
|
||||
#import <FBSnapshotTestCase/UIImage+Compare.h>
|
||||
#import <FBSnapshotTestCase/UIImage+Diff.h>
|
||||
#import <FBSnapshotTestCase/UIImage+Snapshot.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain";
|
||||
NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
|
||||
NSString *const FBReferenceImageKey = @"FBReferenceImageKey";
|
||||
NSString *const FBCapturedImageKey = @"FBCapturedImageKey";
|
||||
NSString *const FBDiffedImageKey = @"FBDiffedImageKey";
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
|
||||
FBTestSnapshotFileNameTypeReference,
|
||||
FBTestSnapshotFileNameTypeFailedReference,
|
||||
FBTestSnapshotFileNameTypeFailedTest,
|
||||
FBTestSnapshotFileNameTypeFailedTestDiff,
|
||||
};
|
||||
|
||||
@implementation FBSnapshotTestController
|
||||
{
|
||||
NSString *_testName;
|
||||
NSFileManager *_fileManager;
|
||||
}
|
||||
|
||||
#pragma mark - Initializers
|
||||
|
||||
- (instancetype)initWithTestClass:(Class)testClass;
|
||||
{
|
||||
return [self initWithTestName:NSStringFromClass(testClass)];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTestName:(NSString *)testName
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_testName = [testName copy];
|
||||
_deviceAgnostic = NO;
|
||||
|
||||
_fileManager = [[NSFileManager alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory];
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self compareSnapshotOfViewOrLayer:layer
|
||||
selector:selector
|
||||
identifier:identifier
|
||||
tolerance:0
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self compareSnapshotOfViewOrLayer:view
|
||||
selector:selector
|
||||
identifier:identifier
|
||||
tolerance:0
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
if (self.recordMode) {
|
||||
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
|
||||
} else {
|
||||
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage *)referenceImageForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
|
||||
if (nil == image && NULL != errorPtr) {
|
||||
BOOL exists = [_fileManager fileExistsAtPath:filePath];
|
||||
if (!exists) {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodeNeedsRecord
|
||||
userInfo:@{
|
||||
FBReferenceImageFilePathKey: filePath,
|
||||
NSLocalizedDescriptionKey: @"Unable to load reference image.",
|
||||
NSLocalizedFailureReasonErrorKey: @"Reference image not found. You need to run the test in record mode",
|
||||
}];
|
||||
} else {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodeUnknown
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
|
||||
toImage:(UIImage *)image
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
BOOL sameImageDimensions = CGSizeEqualToSize(referenceImage.size, image.size);
|
||||
if (sameImageDimensions && [referenceImage fb_compareWithImage:image tolerance:tolerance]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (NULL != errorPtr) {
|
||||
NSString *errorDescription = sameImageDimensions ? @"Images different" : @"Images different sizes";
|
||||
NSString *errorReason = sameImageDimensions ? [NSString stringWithFormat:@"image pixels differed by more than %.2f%% from the reference image", tolerance * 100]
|
||||
: [NSString stringWithFormat:@"referenceImage:%@, image:%@", NSStringFromCGSize(referenceImage.size), NSStringFromCGSize(image.size)];
|
||||
FBSnapshotTestControllerErrorCode errorCode = sameImageDimensions ? FBSnapshotTestControllerErrorCodeImagesDifferent : FBSnapshotTestControllerErrorCodeImagesDifferentSizes;
|
||||
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:errorCode
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: errorDescription,
|
||||
NSLocalizedFailureReasonErrorKey: errorReason,
|
||||
FBReferenceImageKey: referenceImage,
|
||||
FBCapturedImageKey: image,
|
||||
FBDiffedImageKey: [referenceImage fb_diffWithImage:image],
|
||||
}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
|
||||
testImage:(UIImage *)testImage
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
NSData *referencePNGData = UIImagePNGRepresentation(referenceImage);
|
||||
NSData *testPNGData = UIImagePNGRepresentation(testImage);
|
||||
|
||||
NSString *referencePath = [self _failedFilePathForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeFailedReference];
|
||||
|
||||
NSError *creationError = nil;
|
||||
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[referencePath stringByDeletingLastPathComponent]
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:&creationError];
|
||||
if (!didCreateDir) {
|
||||
if (NULL != errorPtr) {
|
||||
*errorPtr = creationError;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *testPath = [self _failedFilePathForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeFailedTest];
|
||||
|
||||
if (![testPNGData writeToFile:testPath options:NSDataWritingAtomic error:errorPtr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *diffPath = [self _failedFilePathForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff];
|
||||
|
||||
UIImage *diffImage = [referenceImage fb_diffWithImage:testImage];
|
||||
NSData *diffImageData = UIImagePNGRepresentation(diffImage);
|
||||
|
||||
if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSLog(@"If you have Kaleidoscope installed you can run this command to see an image diff:\n"
|
||||
@"ksdiff \"%@\" \"%@\"", referencePath, testPath);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Private API
|
||||
|
||||
- (NSString *)_fileNameForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
fileNameType:(FBTestSnapshotFileNameType)fileNameType
|
||||
{
|
||||
NSString *fileName = nil;
|
||||
switch (fileNameType) {
|
||||
case FBTestSnapshotFileNameTypeFailedReference:
|
||||
fileName = @"reference_";
|
||||
break;
|
||||
case FBTestSnapshotFileNameTypeFailedTest:
|
||||
fileName = @"failed_";
|
||||
break;
|
||||
case FBTestSnapshotFileNameTypeFailedTestDiff:
|
||||
fileName = @"diff_";
|
||||
break;
|
||||
default:
|
||||
fileName = @"";
|
||||
break;
|
||||
}
|
||||
fileName = [fileName stringByAppendingString:NSStringFromSelector(selector)];
|
||||
if (0 < identifier.length) {
|
||||
fileName = [fileName stringByAppendingFormat:@"_%@", identifier];
|
||||
}
|
||||
|
||||
if (self.isDeviceAgnostic) {
|
||||
fileName = FBDeviceAgnosticNormalizedFileName(fileName);
|
||||
}
|
||||
|
||||
if ([[UIScreen mainScreen] scale] > 1) {
|
||||
fileName = [fileName stringByAppendingFormat:@"@%.fx", [[UIScreen mainScreen] scale]];
|
||||
}
|
||||
fileName = [fileName stringByAppendingPathExtension:@"png"];
|
||||
return fileName;
|
||||
}
|
||||
|
||||
- (NSString *)_referenceFilePathForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
{
|
||||
NSString *fileName = [self _fileNameForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeReference];
|
||||
NSString *filePath = [_referenceImagesDirectory stringByAppendingPathComponent:_testName];
|
||||
filePath = [filePath stringByAppendingPathComponent:fileName];
|
||||
return filePath;
|
||||
}
|
||||
|
||||
- (NSString *)_failedFilePathForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
fileNameType:(FBTestSnapshotFileNameType)fileNameType
|
||||
{
|
||||
NSString *fileName = [self _fileNameForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:fileNameType];
|
||||
NSString *folderPath = NSTemporaryDirectory();
|
||||
if (getenv("IMAGE_DIFF_DIR")) {
|
||||
folderPath = @(getenv("IMAGE_DIFF_DIR"));
|
||||
}
|
||||
NSString *filePath = [folderPath stringByAppendingPathComponent:_testName];
|
||||
filePath = [filePath stringByAppendingPathComponent:fileName];
|
||||
return filePath;
|
||||
}
|
||||
|
||||
- (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
tolerance:(CGFloat)tolerance
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
|
||||
if (nil != referenceImage) {
|
||||
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
|
||||
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr];
|
||||
if (!imagesSame) {
|
||||
NSError *saveError = nil;
|
||||
if ([self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:&saveError] == NO) {
|
||||
NSLog(@"Error saving test images: %@", saveError);
|
||||
}
|
||||
}
|
||||
return imagesSame;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
|
||||
return [self _saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)_saveReferenceImage:(UIImage *)image
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
BOOL didWrite = NO;
|
||||
if (nil != image) {
|
||||
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
|
||||
NSData *pngData = UIImagePNGRepresentation(image);
|
||||
if (nil != pngData) {
|
||||
NSError *creationError = nil;
|
||||
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:&creationError];
|
||||
if (!didCreateDir) {
|
||||
if (NULL != errorPtr) {
|
||||
*errorPtr = creationError;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr];
|
||||
if (didWrite) {
|
||||
NSLog(@"Reference image save at: %@", filePath);
|
||||
}
|
||||
} else {
|
||||
if (nil != errorPtr) {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodePNGCreationFailed
|
||||
userInfo:@{
|
||||
FBReferenceImageFilePathKey: filePath,
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
return didWrite;
|
||||
}
|
||||
|
||||
- (UIImage *)_imageForViewOrLayer:(id)viewOrLayer
|
||||
{
|
||||
if ([viewOrLayer isKindOfClass:[UIView class]]) {
|
||||
if (_usesDrawViewHierarchyInRect) {
|
||||
return [UIImage fb_imageForView:viewOrLayer];
|
||||
} else {
|
||||
return [UIImage fb_imageForViewLayer:viewOrLayer];
|
||||
}
|
||||
} else if ([viewOrLayer isKindOfClass:[CALayer class]]) {
|
||||
return [UIImage fb_imageForLayer:viewOrLayer];
|
||||
} else {
|
||||
[NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#if swift(>=3)
|
||||
public extension FBSnapshotTestCase {
|
||||
public func FBSnapshotVerifyView(_ view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
|
||||
FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line)
|
||||
}
|
||||
|
||||
public func FBSnapshotVerifyLayer(_ layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
|
||||
FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line)
|
||||
}
|
||||
|
||||
private func FBSnapshotVerifyViewOrLayer(_ viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
|
||||
let envReferenceImageDirectory = self.getReferenceImageDirectory(withDefault: FB_REFERENCE_IMAGE_DIR)
|
||||
var error: NSError?
|
||||
var comparisonSuccess = false
|
||||
|
||||
if let envReferenceImageDirectory = envReferenceImageDirectory {
|
||||
for suffix in suffixes {
|
||||
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
|
||||
if viewOrLayer.isKind(of: UIView.self) {
|
||||
do {
|
||||
try compareSnapshot(of: viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
|
||||
comparisonSuccess = true
|
||||
} catch let error1 as NSError {
|
||||
error = error1
|
||||
comparisonSuccess = false
|
||||
}
|
||||
} else if viewOrLayer.isKind(of: CALayer.self) {
|
||||
do {
|
||||
try compareSnapshot(of: viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
|
||||
comparisonSuccess = true
|
||||
} catch let error1 as NSError {
|
||||
error = error1
|
||||
comparisonSuccess = false
|
||||
}
|
||||
} else {
|
||||
assertionFailure("Only UIView and CALayer classes can be snapshotted")
|
||||
}
|
||||
|
||||
assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line)
|
||||
|
||||
if comparisonSuccess || recordMode {
|
||||
break
|
||||
}
|
||||
|
||||
assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line)
|
||||
}
|
||||
} else {
|
||||
XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(_ assertion: Bool, message: String, file: StaticString, line: UInt) {
|
||||
if !assertion {
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
public extension FBSnapshotTestCase {
|
||||
public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
|
||||
FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line)
|
||||
}
|
||||
|
||||
public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
|
||||
FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line)
|
||||
}
|
||||
|
||||
private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
|
||||
let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR)
|
||||
var error: NSError?
|
||||
var comparisonSuccess = false
|
||||
|
||||
if let envReferenceImageDirectory = envReferenceImageDirectory {
|
||||
for suffix in suffixes {
|
||||
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
|
||||
if viewOrLayer.isKindOfClass(UIView) {
|
||||
do {
|
||||
try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
|
||||
comparisonSuccess = true
|
||||
} catch let error1 as NSError {
|
||||
error = error1
|
||||
comparisonSuccess = false
|
||||
}
|
||||
} else if viewOrLayer.isKindOfClass(CALayer) {
|
||||
do {
|
||||
try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
|
||||
comparisonSuccess = true
|
||||
} catch let error1 as NSError {
|
||||
error = error1
|
||||
comparisonSuccess = false
|
||||
}
|
||||
} else {
|
||||
assertionFailure("Only UIView and CALayer classes can be snapshotted")
|
||||
}
|
||||
|
||||
assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line)
|
||||
|
||||
if comparisonSuccess || recordMode {
|
||||
break
|
||||
}
|
||||
|
||||
assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line)
|
||||
}
|
||||
} else {
|
||||
XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(assertion: Bool, message: String, file: StaticString, line: UInt) {
|
||||
if !assertion {
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Generated
-29
@@ -1,29 +0,0 @@
|
||||
BSD License
|
||||
|
||||
For the FBSnapshotTestCase software
|
||||
|
||||
Copyright (c) 2013, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
FBSnapshotTestCase
|
||||
======================
|
||||
|
||||
[](https://travis-ci.org/facebook/ios-snapshot-test-case) [](http://cocoadocs.org/docsets/FBSnapshotTestCase/)
|
||||
|
||||
What it does
|
||||
------------
|
||||
|
||||
A "snapshot test case" takes a configured `UIView` or `CALayer` and uses the
|
||||
`renderInContext:` method to get an image snapshot of its contents. It
|
||||
compares this snapshot to a "reference image" stored in your source code
|
||||
repository and fails the test if the two images don't match.
|
||||
|
||||
Why?
|
||||
----
|
||||
|
||||
At Facebook we write a lot of UI code. As you might imagine, each type of
|
||||
feed story is rendered using a subclass of `UIView`. There are a lot of edge
|
||||
cases that we want to handle correctly:
|
||||
|
||||
- What if there is more text than can fit in the space available?
|
||||
- What if an image doesn't match the size of an image view?
|
||||
- What should the highlighted state look like?
|
||||
|
||||
It's straightforward to test logic code, but less obvious how you should test
|
||||
views. You can do a lot of rectangle asserts, but these are hard to understand
|
||||
or visualize. Looking at an image diff shows you exactly what changed and how
|
||||
it will look to users.
|
||||
|
||||
We developed `FBSnapshotTestCase` to make snapshot tests easy.
|
||||
|
||||
Installation with CocoaPods
|
||||
---------------------------
|
||||
|
||||
1. Add the following lines to your Podfile:
|
||||
|
||||
```
|
||||
target "Tests" do
|
||||
pod 'FBSnapshotTestCase'
|
||||
end
|
||||
```
|
||||
|
||||
If you support iOS 7 use `FBSnapshotTestCase/Core` instead, which doesn't contain Swift support.
|
||||
|
||||
Replace "Tests" with the name of your test project.
|
||||
|
||||
2. There are [three ways](https://github.com/facebook/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/FBSnapshotTestCase.h#L19-L29) of setting reference image directories, the recommended one is to define `FB_REFERENCE_IMAGE_DIR` in your scheme. This should point to the directory where you want reference images to be stored. At Facebook, we normally use this:
|
||||
|
||||
|Name|Value|
|
||||
|:---|:----|
|
||||
|`FB_REFERENCE_IMAGE_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages`|
|
||||
|
||||
|
||||

|
||||
|
||||
Creating a snapshot test
|
||||
------------------------
|
||||
|
||||
1. Subclass `FBSnapshotTestCase` instead of `XCTestCase`.
|
||||
2. From within your test, use `FBSnapshotVerifyView`.
|
||||
3. Run the test once with `self.recordMode = YES;` in the test's `-setUp`
|
||||
method. (This creates the reference images on disk.)
|
||||
4. Remove the line enabling record mode and run the test.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Automatically names reference images on disk according to test class and
|
||||
selector.
|
||||
- Prints a descriptive error message to the console on failure. (Bonus:
|
||||
failure message includes a one-line command to see an image diff if
|
||||
you have [Kaleidoscope](http://www.kaleidoscopeapp.com) installed.)
|
||||
- Supply an optional "identifier" if you want to perform multiple snapshots
|
||||
in a single test method.
|
||||
- Support for `CALayer` via `FBSnapshotVerifyLayer`.
|
||||
- `usesDrawViewHierarchyInRect` to handle cases like `UIVisualEffect`, `UIAppearance` and Size Classes.
|
||||
- `isDeviceAgnostic` to allow appending the device model (`iPhone`, `iPad`, `iPod Touch`, etc), OS version and screen size to the images (allowing to have multiple tests for the same «snapshot» for different `OS`s and devices).
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
Your unit test must be an "application test", not a "logic test." (That is, it
|
||||
must be run within the Simulator so that it has access to UIKit.) In Xcode 5
|
||||
and later new projects only offer application tests, but older projects will
|
||||
have separate targets for the two types.
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
`FBSnapshotTestCase` was written at Facebook by
|
||||
[Jonathan Dann](https://facebook.com/j.p.dann) with significant contributions by
|
||||
[Todd Krabach](https://facebook.com/toddkrabach).
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
`FBSnapshotTestCase` is BSD-licensed. See `LICENSE`.
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "ContainerControllerSwift",
|
||||
"version": "0.1.0",
|
||||
"summary": "A short description of ContainerControllerSwift.",
|
||||
"description": "TODO: Add long description of the pod here.",
|
||||
"homepage": "https://github.com/rustamburger@gmail.com/ContainerControllerSwift",
|
||||
"version": "1.0.0",
|
||||
"summary": "This is a swipe-panel from application: https://www.apple.com/ios/maps/",
|
||||
"description": "TODO: Add long description of the pod here.\n'This is a swipe-panel from application: https://www.apple.com/ios/maps/'",
|
||||
"homepage": "https://github.com/mrustaa/ContainerController",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"file": "LICENSE"
|
||||
@@ -12,11 +12,13 @@
|
||||
"rustamburger@gmail.com": "rustamburger@gmail.com"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/rustamburger@gmail.com/ContainerControllerSwift.git",
|
||||
"tag": "0.1.0"
|
||||
"git": "https://github.com/mrustaa/ContainerController.git",
|
||||
"tag": "1.0.0"
|
||||
},
|
||||
"swift_versions": "5.2.4",
|
||||
"platforms": {
|
||||
"ios": "8.0"
|
||||
"ios": "13.0"
|
||||
},
|
||||
"source_files": "ContainerControllerSwift/Classes/**/*"
|
||||
"source_files": "ContainerControllerSwift/**/*.{swift}",
|
||||
"swift_version": "5.2.4"
|
||||
}
|
||||
|
||||
Generated
+3
-14
@@ -1,27 +1,16 @@
|
||||
PODS:
|
||||
- ContainerControllerSwift (0.1.0)
|
||||
- FBSnapshotTestCase (2.1.4):
|
||||
- FBSnapshotTestCase/SwiftSupport (= 2.1.4)
|
||||
- FBSnapshotTestCase/Core (2.1.4)
|
||||
- FBSnapshotTestCase/SwiftSupport (2.1.4):
|
||||
- FBSnapshotTestCase/Core
|
||||
- ContainerControllerSwift (1.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- ContainerControllerSwift (from `../`)
|
||||
- FBSnapshotTestCase (~> 2.1.4)
|
||||
|
||||
SPEC REPOS:
|
||||
https://cdn.cocoapods.org/:
|
||||
- FBSnapshotTestCase
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
ContainerControllerSwift:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
ContainerControllerSwift: d9165e33edebeea0b8e0992717b0811328400d3c
|
||||
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
|
||||
ContainerControllerSwift: 784bd547f936a51b956788cdd23869344e638119
|
||||
|
||||
PODFILE CHECKSUM: 748541873723babe6decea03fbfa834df5db1b55
|
||||
PODFILE CHECKSUM: 5f9e7bdcf65f6b742444242add916867543ae8c0
|
||||
|
||||
COCOAPODS: 1.9.3
|
||||
|
||||
+427
-683
File diff suppressed because it is too large
Load Diff
Generated
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_FBSnapshotTestCase : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_FBSnapshotTestCase
|
||||
@end
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#import "FBSnapshotTestCase.h"
|
||||
#import "FBSnapshotTestCasePlatform.h"
|
||||
#import "FBSnapshotTestController.h"
|
||||
|
||||
FOUNDATION_EXPORT double FBSnapshotTestCaseVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char FBSnapshotTestCaseVersionString[];
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase
|
||||
ENABLE_BITCODE = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/FBSnapshotTestCase
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib"
|
||||
SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
framework module FBSnapshotTestCase {
|
||||
umbrella header "FBSnapshotTestCase-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase
|
||||
ENABLE_BITCODE = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/FBSnapshotTestCase
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib"
|
||||
SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
-33
@@ -1,36 +1,3 @@
|
||||
# Acknowledgements
|
||||
This application makes use of the following third party libraries:
|
||||
|
||||
## FBSnapshotTestCase
|
||||
|
||||
BSD License
|
||||
|
||||
For the FBSnapshotTestCase software
|
||||
|
||||
Copyright (c) 2013, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Generated by CocoaPods - https://cocoapods.org
|
||||
|
||||
-39
@@ -12,45 +12,6 @@
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>BSD License
|
||||
|
||||
For the FBSnapshotTestCase software
|
||||
|
||||
Copyright (c) 2013, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</string>
|
||||
<key>License</key>
|
||||
<string>BSD</string>
|
||||
<key>Title</key>
|
||||
<string>FBSnapshotTestCase</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Generated by CocoaPods - https://cocoapods.org</string>
|
||||
|
||||
+3
-6
@@ -1,10 +1,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift/ContainerControllerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -framework "ContainerControllerSwift" -framework "FBSnapshotTestCase" -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift/ContainerControllerSwift.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "ContainerControllerSwift"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
+3
-6
@@ -1,10 +1,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift/ContainerControllerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -framework "ContainerControllerSwift" -framework "FBSnapshotTestCase" -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ContainerControllerSwift/ContainerControllerSwift.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "ContainerControllerSwift"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
# ContainerControllerSwift
|
||||
|
||||
[](https://travis-ci.org/rustamburger@gmail.com/ContainerControllerSwift)
|
||||
[](https://travis-ci.org/github/mrustaa/ContainerController)
|
||||
[](https://cocoapods.org/pods/ContainerControllerSwift)
|
||||
[](https://cocoapods.org/pods/ContainerControllerSwift)
|
||||
[](https://cocoapods.org/pods/ContainerControllerSwift)
|
||||
[](https://swift.org/)
|
||||
[](https://swift.org/)
|
||||
|
||||
## Example
|
||||
# ContainerController
|
||||
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
This is a swipe-panel from application: https://www.apple.com/ios/maps/
|
||||
|
||||
## Requirements
|
||||
<!-- TOC -->
|
||||
|
||||
## Installation
|
||||
- [Getting Started](#getting-started)
|
||||
- [Delegate ScrollView to self](#delegate-scrollview-to-self)
|
||||
- [Action](#action)
|
||||
- [Move position with an animation](#move-position-with-an-animation)
|
||||
- [Adding custom views in ContainerController](#adding-custom-views-in-containercontroller)
|
||||
- [Add `ScrollView`](#add-scrollview)
|
||||
- [Add `HeaderView`](#add-headerview)
|
||||
- [Add `FooterView`](#add-footerview)
|
||||
- [Settings](#settings)
|
||||
- [Layout](#layout)
|
||||
- [Customize the layout with create subclass ContainerLayout on initialization](#customize-the-layout-with-create-subclass-containerlayout-on-initialization)
|
||||
- [Or create object ContainerLayout](#or-create-object-containerlayout)
|
||||
- [Change settings right away](#change-settings-right-away)
|
||||
- [ContainerView](#containerview)
|
||||
- [Use a ready-made solution](#use-a-ready-made-solution)
|
||||
- [Add your changes](#add-your-changes)
|
||||
- [More details](#more-details)
|
||||
- [Change positions on screen Top Middle Bottom](#change-positions-on-screen-top-middle-bottom)
|
||||
- [Customize indentations for View](#customize-indentations-for-view)
|
||||
- [Customize for landscape orientation](#customize-for-landscape-orientation)
|
||||
- [Parameters for control footerView](#parameters-for-control-footerview)
|
||||
- [ContainerControllerDelegate](#containercontrollerdelegate)
|
||||
- [License](#license)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Installation CocoaPods
|
||||
|
||||
ContainerControllerSwift is available through [CocoaPods](https://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
@@ -20,10 +46,395 @@ it, simply add the following line to your Podfile:
|
||||
pod 'ContainerControllerSwift'
|
||||
```
|
||||
|
||||
## Author
|
||||
## Getting Started
|
||||
|
||||
rustamburger@gmail.com, rustamburger@gmail.com
|
||||
```swift
|
||||
import UIKit
|
||||
|
||||
class ViewController: UIViewController, ContainerControllerDelegate {
|
||||
|
||||
var container: ContainerController!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Create ContainerController Layout object
|
||||
let layout = ContainerLayout()
|
||||
layout.startPosition = .hide
|
||||
layout.backgroundShadowShow = true
|
||||
layout.positions = ContainerPosition(top: 70, middle: 250, bottom: 70)
|
||||
|
||||
// Create ContainerController object, along with the container.view
|
||||
// Pass the current UIViewController
|
||||
container = ContainerController(addTo: self, layout: layout)
|
||||
container.view.cornerRadius = 15
|
||||
container.view.addShadow()
|
||||
|
||||
// Create subclass scrollView
|
||||
let tableView = UITableView()
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
|
||||
// Add scrollView to container
|
||||
container.add(scrollView: tableView)
|
||||
|
||||
// Finishing settings ContainerController,
|
||||
// Animated move position Top
|
||||
container.move(type: .top)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
// Remove the subviews ContainerController
|
||||
container.remove()
|
||||
container = nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Delegate ScrollView to self
|
||||
#### ☝️ If you implement delegate ScrollView (TableView, CollectionView, TextView) to `self`, then you need to call 4 functions in ContainerController
|
||||
|
||||
```swift
|
||||
extension ViewController: UIScrollViewDelegate {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
container.scrollViewDidScroll(scrollView)
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
container.scrollViewWillBeginDragging(scrollView)
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
container.scrollViewDidEndDecelerating(scrollView)
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
container.scrollViewDidEndDragging(scrollView, willDecelerate: decelerate)
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: UITableViewDelegate {
|
||||
...
|
||||
}
|
||||
|
||||
extension ViewController: UITableViewDataSource {
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Action
|
||||
|
||||
### Move position with an animation
|
||||
|
||||
```swift
|
||||
|
||||
container.move(type: .top)
|
||||
container.move(type: .middle)
|
||||
container.move(type: .bottom)
|
||||
|
||||
```
|
||||
|
||||
## Adding custom views in ContainerController
|
||||
|
||||
### Add `ScrollView`
|
||||
|
||||
```swift
|
||||
|
||||
let tableView = UITableView()
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.tableFooterView = UIView()
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
|
||||
// Add scrollView to container
|
||||
container.add(scrollView: tableView)
|
||||
|
||||
```
|
||||
|
||||
### Add `HeaderView`
|
||||
|
||||
```swift
|
||||
|
||||
let headerView = ExampleHeaderGripView()
|
||||
headerView.height = 20
|
||||
|
||||
container.add(headerView: headerView)
|
||||
|
||||
```
|
||||
|
||||
### Add `FooterView`
|
||||
|
||||
```swift
|
||||
|
||||
let tabBarView = HeaderTabBarView()
|
||||
tabBarView.height = 49.0
|
||||
|
||||
container.add(footerView: tabBarView)
|
||||
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
### Layout
|
||||
|
||||
#### Customize the layout with create subclass `ContainerLayout` on initialization
|
||||
|
||||
```swift
|
||||
|
||||
class NewContainerLayout: ContainerLayout {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
// Initialization start position.
|
||||
startPosition = .hide
|
||||
|
||||
// Disables any moving with gestures.
|
||||
movingEnabled = true
|
||||
|
||||
// Sets the new value for positions of animated movement (top, middle, bottom).
|
||||
positions = ContainerPosition(top: 70, middle: 250, bottom: 70)
|
||||
|
||||
// Sets insets container.view (left, right).
|
||||
insets = ContainerInsets(right: 20, left: 20)
|
||||
}
|
||||
}
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
var container: ContainerController!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
container = ContainerController(addTo: self, layout: NewContainerLayout())
|
||||
container.move(type: .top)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Or create object `ContainerLayout`
|
||||
|
||||
```swift
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Create ContainerController Layout object
|
||||
let layout = ContainerLayout()
|
||||
layout.startPosition = .hide
|
||||
layout.backgroundShadowShow = true
|
||||
layout.positions = ContainerPosition(top: 70, middle: 250, bottom: 70)
|
||||
|
||||
container = ContainerController(addTo: self, layout: layout)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Change settings right away
|
||||
|
||||
|
||||
```swift
|
||||
|
||||
// Properties
|
||||
container.set(movingEnabled: true)
|
||||
container.set(trackingPosition: false)
|
||||
container.set(footerPadding: 100)
|
||||
|
||||
// Add ScrollInsets Top/Bottom
|
||||
container.set(scrollIndicatorTop: 5) // ↓
|
||||
container.set(scrollIndicatorBottom: 5) // ↑
|
||||
|
||||
// Positions
|
||||
container.set(top: 70) // ↓
|
||||
container.set(middle: 250) // ↑
|
||||
container.set(bottom: 80) // ↑
|
||||
|
||||
// Middle Enable/Disable
|
||||
container.set(middle: 250)
|
||||
container.set(middle: nil)
|
||||
|
||||
// Background Shadow
|
||||
container.set(backgroundShadowShow: true)
|
||||
|
||||
// Insets View
|
||||
container.set(left: 5) // →
|
||||
container.set(right: 5) // ←
|
||||
|
||||
// Landscape params
|
||||
container.setLandscape(top: 30)
|
||||
container.setLandscape(middle: 150)
|
||||
container.setLandscape(bottom: 70)
|
||||
container.setLandscape(middle: nil)
|
||||
|
||||
container.setLandscape(backgroundShadowShow: false)
|
||||
|
||||
container.setLandscape(left: 10)
|
||||
container.setLandscape(right: 100)
|
||||
|
||||
```
|
||||
|
||||
## ContainerView
|
||||
|
||||
ContainerView is generated automatically when you create ContainerController
|
||||
Use a ready-made solution to change the radius, add shadow, and blur.
|
||||
|
||||
### Use a ready-made solution
|
||||
|
||||
```swift
|
||||
|
||||
// Change cornerRadius global for all subviews
|
||||
container.view.cornerRadius = 15
|
||||
|
||||
// Add layer shadow
|
||||
container.view.addShadow(opacity: 0.1)
|
||||
|
||||
// Add background blur UIVisualEffectView
|
||||
container.view.addBlur(style: .dark)
|
||||
|
||||
```
|
||||
#### Add your changes
|
||||
|
||||
```swift
|
||||
|
||||
// Add custom shadow
|
||||
let layer = container.view.layer
|
||||
layer.shadowOpacity = 0.5
|
||||
layer.shadowColor = UIColor.red.cgColor
|
||||
layer.shadowOffset = CGSize(width: 1, height: 4)
|
||||
layer.shadowRadius = 5
|
||||
|
||||
// Add view in container.view
|
||||
let viewRed = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
|
||||
viewRed.backgroundColor = .systemRed
|
||||
container.view.addSubview(viewRed)
|
||||
|
||||
// Add view under scrollView container.view
|
||||
let viewGreen = UIView(frame: CGRect(x: 25, y: 25, width: 50, height: 50))
|
||||
viewGreen.backgroundColor = .systemGreen
|
||||
container.view.insertSubview(viewGreen, at: 0)
|
||||
|
||||
```
|
||||
|
||||
### More details
|
||||
|
||||
#### Change positions on screen Top Middle Bottom
|
||||
|
||||
```swift
|
||||
|
||||
// These parameters set the new position value.
|
||||
container.layout.positions = ContainerPosition(top: 70, middle: 250, bottom: 70)
|
||||
|
||||
// Change settings right away
|
||||
container.set(top: 70) // ↓
|
||||
container.set(middle: 250) // ↑
|
||||
container.set(bottom: 80) // ↑
|
||||
|
||||
```
|
||||
|
||||
#### Customize indentations for View
|
||||
|
||||
```swift
|
||||
|
||||
// Sets insets container.view (left, right).
|
||||
container.layout.insets = ContainerInsets(right: 20, left: 20)
|
||||
|
||||
container.layout.landscapeInsets = ContainerInsets(right: 20, left: 100)
|
||||
|
||||
|
||||
// Change settings right away
|
||||
container.set(left: 5) // →
|
||||
container.set(right: 5) // ←
|
||||
|
||||
container.setLandscape(left: 10)
|
||||
container.setLandscape(right: 100)
|
||||
|
||||
```
|
||||
|
||||
#### Customize for landscape orientation
|
||||
|
||||
```swift
|
||||
|
||||
// Sets the background shadow under container. (Default: backgroundShadowShow).
|
||||
container.layout.landscapeBackgroundShadowShow = false
|
||||
|
||||
// Sets the new value for positions of animated movement (top, middle, bottom). (Default: positions).
|
||||
container.layout.landscapePositions = ContainerPosition(top: 20, middle: 150, bottom: 70)
|
||||
|
||||
// Sets insets container.view (left, right). (Default: insets).
|
||||
container.layout.landscapeInsets = ContainerInsets(right: 20, left: 100)
|
||||
|
||||
|
||||
// Change settings right away
|
||||
|
||||
container.setLandscape(top: 30)
|
||||
container.setLandscape(middle: 150)
|
||||
container.setLandscape(bottom: 70)
|
||||
container.setLandscape(middle: nil)
|
||||
|
||||
container.setLandscape(backgroundShadowShow: false)
|
||||
|
||||
container.setLandscape(left: 10)
|
||||
container.setLandscape(right: 100)
|
||||
|
||||
```
|
||||
|
||||
#### Parameters for control footerView
|
||||
|
||||
```swift
|
||||
|
||||
// Padding-top from container.view, if headerView is added, then its + height is summed.
|
||||
container.layout.footerPadding = 100
|
||||
|
||||
// Tracking position container.view during animated movement.
|
||||
container.layout.trackingPosition = false
|
||||
|
||||
// Change settings right away
|
||||
|
||||
container.set(footerPadding: 100)
|
||||
container.set(trackingPosition: false)
|
||||
|
||||
```
|
||||
|
||||
## ContainerControllerDelegate
|
||||
|
||||
```swift
|
||||
|
||||
class ViewController: UIViewController, ContainerControllerDelegate {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let container = ContainerController(addTo: self, layout: layout)
|
||||
container.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports rotation and orientation changes
|
||||
func containerControllerRotation(_ containerController: ContainerController) {
|
||||
...
|
||||
}
|
||||
|
||||
/// Reports a click on the background shadow
|
||||
func containerControllerShadowClick(_ containerController: ContainerController) {
|
||||
...
|
||||
}
|
||||
|
||||
/// Reports the changes current position of the container, after its use
|
||||
func containerControllerMove(_ containerController: ContainerController, position: CGFloat, type: ContainerMoveType, animation: Bool) {
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
ContainerControllerSwift is available under the MIT license. See the LICENSE file for more info.
|
||||
ContainerController is available under the MIT license. See the LICENSE file for more info.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user