├── RippleScrollBarDemo ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── AppDelegate.swift ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard ├── SceneDelegate.swift ├── ViewController.swift └── RippleScrollBar.swift ├── RippleScrollBarDemo.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── someshkarthik.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj └── README.md /RippleScrollBarDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RippleScrollBarDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RippleScrollBarDemo 2 | A demo project to explain the usage of CAReplicatorLayer to create RippleScrollBar component from CRED App 3 | 4 | # Preview 5 | https://user-images.githubusercontent.com/87409736/192079251-48138e46-aa16-4e56-b82b-8bcbca8bd4f6.mp4 6 | 7 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RippleScrollBarDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RippleScrollBarDemo.xcodeproj/xcuserdata/someshkarthik.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RippleScrollBarDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RippleScrollBarDemo 4 | // 5 | // Created by Somesh Karthik on 23/09/22. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // RippleScrollBarDemo 4 | // 5 | // Created by Somesh Karthik on 23/09/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // RippleScrollBarDemo 4 | // 5 | // Created by Somesh Karthik on 23/09/22. 6 | // 7 | 8 | import UIKit 9 | 10 | final class ViewController: UIViewController { 11 | private let collectionView: UICollectionView = { 12 | let layout = UICollectionViewFlowLayout() 13 | layout.scrollDirection = .horizontal 14 | layout.itemSize = CGSize(width: 250, height: 300) 15 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 16 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: String(describing: UICollectionViewCell.self)) 17 | collectionView.showsHorizontalScrollIndicator = false 18 | collectionView.contentInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 15) 19 | return collectionView 20 | }() 21 | private let scrollBar = RippleScrollBar() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | // Do any additional setup after loading the view. 26 | 27 | view.backgroundColor = UIColor(red: 241/255, green: 237/255, blue: 228/255, alpha: 1.0) 28 | 29 | collectionView.delegate = self 30 | collectionView.backgroundColor = view.backgroundColor 31 | collectionView.dataSource = self 32 | 33 | view.addSubview(collectionView) 34 | collectionView.translatesAutoresizingMaskIntoConstraints = false 35 | NSLayoutConstraint.activate([ 36 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 37 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 38 | collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor), 39 | collectionView.heightAnchor.constraint(equalToConstant: 300) 40 | ]) 41 | 42 | view.addSubview(scrollBar) 43 | scrollBar.translatesAutoresizingMaskIntoConstraints = false 44 | NSLayoutConstraint.activate([ 45 | scrollBar.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor), 46 | scrollBar.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20), 47 | scrollBar.widthAnchor.constraint(equalToConstant: 200), 48 | scrollBar.heightAnchor.constraint(equalToConstant: 40) 49 | ]) 50 | 51 | view.layoutIfNeeded() 52 | } 53 | 54 | override func viewWillAppear(_ animated: Bool) { 55 | super.viewWillAppear(animated) 56 | scrollViewDidScroll(collectionView) 57 | } 58 | } 59 | 60 | extension ViewController: UICollectionViewDataSource { 61 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 62 | 10 63 | } 64 | 65 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 66 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: UICollectionViewCell.self), for: indexPath) 67 | cell.backgroundColor = UIColor.white 68 | cell.layer.cornerRadius = 5 69 | cell.layer.borderColor = UIColor.gray.cgColor 70 | cell.layer.borderWidth = 1 71 | return cell 72 | } 73 | } 74 | 75 | extension ViewController: UICollectionViewDelegate { 76 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 77 | let contentInset = scrollView.contentInset 78 | let contentOffset = scrollView.contentOffset 79 | let contentSize = scrollView.contentSize 80 | let currentContentOffset = contentOffset.x + contentInset.left 81 | 82 | let totalContentOffset = contentSize.width + contentInset.left + contentInset.right - view.frame.width 83 | 84 | scrollBar.setPercentage(max(min(currentContentOffset/totalContentOffset, 1.0), 0)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /RippleScrollBarDemo/RippleScrollBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RippleScrollBar.swift 3 | // RippleScrollBarDemo 4 | // 5 | // Created by Somesh Karthik on 23/09/22. 6 | // 7 | 8 | import UIKit 9 | 10 | final class RippleScrollBar: UIView { 11 | private let leftInstance = RippleReplicatorInstance() 12 | private let rightInstance = RippleReplicatorInstance() 13 | 14 | override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | setup() 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | super.init(coder: coder) 21 | setup() 22 | } 23 | 24 | func setup() { 25 | addSubview(leftInstance) 26 | addSubview(rightInstance) 27 | 28 | leftInstance.clipsToBounds = true 29 | rightInstance.clipsToBounds = true 30 | leftInstance.tintColor = UIColor(red: 114/255, green: 41/255, blue: 217/255, alpha: 1.0) 31 | leftInstance.direction = .left 32 | leftInstance.rippleLineWidth = 4 33 | 34 | rightInstance.direction = .right 35 | rightInstance.rippleLineWidth = leftInstance.rippleLineWidth 36 | rightInstance.tintColor = leftInstance.tintColor 37 | 38 | leftInstance.translatesAutoresizingMaskIntoConstraints = false 39 | NSLayoutConstraint.activate([ 40 | leftInstance.leadingAnchor.constraint(equalTo: leadingAnchor), 41 | leftInstance.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), 42 | leftInstance.bottomAnchor.constraint(equalTo: bottomAnchor), 43 | leftInstance.topAnchor.constraint(equalTo: topAnchor) 44 | ]) 45 | 46 | rightInstance.translatesAutoresizingMaskIntoConstraints = false 47 | NSLayoutConstraint.activate([ 48 | rightInstance.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), 49 | rightInstance.trailingAnchor.constraint(equalTo: trailingAnchor), 50 | rightInstance.bottomAnchor.constraint(equalTo: bottomAnchor), 51 | rightInstance.topAnchor.constraint(equalTo: topAnchor) 52 | ]) 53 | } 54 | 55 | func setPercentage(_ percentage: CGFloat) { 56 | rightInstance.setOffsetPercentage(percentage) 57 | leftInstance.setOffsetPercentage(percentage) 58 | } 59 | } 60 | 61 | private final class RippleReplicatorInstance: UIView { 62 | enum Direction { 63 | case left 64 | case right 65 | } 66 | 67 | override class var layerClass: AnyClass { 68 | CAReplicatorLayer.self 69 | } 70 | 71 | // MARK: Private Properties 72 | private var replicatorLayer: CAReplicatorLayer { 73 | layer as! CAReplicatorLayer 74 | } 75 | 76 | //MARK:- View Properties 77 | private let rippleShape = CAShapeLayer() 78 | 79 | private let rippleTranslation: CGFloat = 50 80 | 81 | //MARK:- Size constants and Margins 82 | private var arcRadius: CGFloat { 83 | bounds.height/2 84 | } 85 | 86 | private var rippleOffset: CGFloat { 87 | rippleSize.width - arcRadius 88 | } 89 | 90 | private let numberOfRipples: Int = 3 91 | 92 | // MARK: Public Properties 93 | override var tintColor: UIColor! { 94 | didSet { 95 | updateRipple() 96 | } 97 | } 98 | 99 | var direction: Direction = .left { 100 | didSet { 101 | updateRipple() 102 | } 103 | } 104 | 105 | var rippleSize: CGSize { 106 | let rippleWidth = bounds.width/CGFloat(numberOfRipples) 107 | return CGSize(width: rippleWidth + arcRadius*0.35, height: bounds.height) 108 | } 109 | 110 | /// Use this property to change the linewidth of child ripples 111 | /// Default value is `1` 112 | var rippleLineWidth: CGFloat = 1 { 113 | didSet { 114 | updateRipple() 115 | } 116 | } 117 | 118 | override init(frame: CGRect) { 119 | super.init(frame: frame) 120 | setup() 121 | } 122 | 123 | required init?(coder: NSCoder) { 124 | super.init(coder: coder) 125 | setup() 126 | } 127 | 128 | // MARK: Public Functions 129 | func setup() { 130 | rippleShape.fillColor = nil 131 | replicatorLayer.addSublayer(rippleShape) 132 | replicatorLayer.instanceCount = numberOfRipples 133 | } 134 | 135 | /// It will update the spacing between each ripples by taking 136 | /// ``direction``, `rippleOffset`` and ``rippleOffsetPercentage`` applied to `x` 137 | func setOffsetPercentage(_ percentage: CGFloat) { 138 | CATransaction.begin() 139 | CATransaction.setDisableActions(true) 140 | switch direction { 141 | case .left: 142 | replicatorLayer.instanceTransform = CATransform3DMakeTranslation(rippleOffset * percentage, 0, 0) 143 | case .right: 144 | replicatorLayer.instanceTransform = CATransform3DMakeTranslation(-1 * rippleOffset * (1-percentage), 0, 0) 145 | } 146 | CATransaction.commit() 147 | } 148 | 149 | override func draw(_ rect: CGRect) { 150 | super.draw(rect) 151 | 152 | let path: UIBezierPath 153 | switch direction { 154 | case .left: 155 | path = UIBezierPath(arcCenter: CGPoint(x: arcRadius, y: rippleSize.height/2), radius: rippleSize.height/2 - rippleLineWidth/2, startAngle: 3 * .pi/2, endAngle: .pi/2, clockwise: false) 156 | 157 | path.addLine(to: CGPoint(x: rippleSize.width, y: bounds.height-rippleLineWidth/2)) 158 | path.move(to: CGPoint(x: arcRadius, y: rippleLineWidth/2)) 159 | path.addLine(to: CGPoint(x: rippleSize.width, y: rippleLineWidth/2)) 160 | 161 | case .right: 162 | let arcOriginX = rect.width - arcRadius 163 | 164 | path = UIBezierPath(arcCenter: CGPoint(x: arcOriginX, y: rippleSize.height/2), radius: rippleSize.height/2 - rippleLineWidth/2, startAngle: 3 * .pi/2, endAngle: .pi/2, clockwise: direction == .right) 165 | 166 | path.addLine(to: CGPoint(x: 0, y: bounds.height-rippleLineWidth/2)) 167 | path.move(to: CGPoint(x: arcOriginX, y: rippleLineWidth/2)) 168 | path.addLine(to: CGPoint(x: 0, y: rippleLineWidth/2)) 169 | 170 | } 171 | 172 | rippleShape.path = path.cgPath 173 | } 174 | 175 | // MARK: Private Functions 176 | 177 | /// It will update the ripple related properties and frame 178 | private func updateRipple() { 179 | rippleShape.strokeColor = tintColor.cgColor 180 | rippleShape.lineWidth = rippleLineWidth 181 | let xOrigin = direction == .left ? 0 : bounds.width - rippleSize.width 182 | rippleShape.frame = CGRect(x: xOrigin, y: 0, width: rippleSize.width, height: rippleSize.height) 183 | } 184 | } 185 | 186 | -------------------------------------------------------------------------------- /RippleScrollBarDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 87C4E78E28DE0B78005955DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C4E78D28DE0B78005955DA /* AppDelegate.swift */; }; 11 | 87C4E79028DE0B78005955DA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C4E78F28DE0B78005955DA /* SceneDelegate.swift */; }; 12 | 87C4E79228DE0B78005955DA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C4E79128DE0B78005955DA /* ViewController.swift */; }; 13 | 87C4E79528DE0B78005955DA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87C4E79328DE0B78005955DA /* Main.storyboard */; }; 14 | 87C4E79728DE0B79005955DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87C4E79628DE0B79005955DA /* Assets.xcassets */; }; 15 | 87C4E79A28DE0B79005955DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87C4E79828DE0B79005955DA /* LaunchScreen.storyboard */; }; 16 | 87C4E7A228DE21F2005955DA /* RippleScrollBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C4E7A128DE21F2005955DA /* RippleScrollBar.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 87C4E78A28DE0B78005955DA /* RippleScrollBarDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RippleScrollBarDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 87C4E78D28DE0B78005955DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 87C4E78F28DE0B78005955DA /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 23 | 87C4E79128DE0B78005955DA /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 87C4E79428DE0B78005955DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 87C4E79628DE0B79005955DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 87C4E79928DE0B79005955DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 87C4E79B28DE0B79005955DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 87C4E7A128DE21F2005955DA /* RippleScrollBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RippleScrollBar.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 87C4E78728DE0B78005955DA /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 87C4E78128DE0B78005955DA = { 43 | isa = PBXGroup; 44 | children = ( 45 | 87C4E78C28DE0B78005955DA /* RippleScrollBarDemo */, 46 | 87C4E78B28DE0B78005955DA /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 87C4E78B28DE0B78005955DA /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 87C4E78A28DE0B78005955DA /* RippleScrollBarDemo.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 87C4E78C28DE0B78005955DA /* RippleScrollBarDemo */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 87C4E78D28DE0B78005955DA /* AppDelegate.swift */, 62 | 87C4E78F28DE0B78005955DA /* SceneDelegate.swift */, 63 | 87C4E79128DE0B78005955DA /* ViewController.swift */, 64 | 87C4E79328DE0B78005955DA /* Main.storyboard */, 65 | 87C4E79628DE0B79005955DA /* Assets.xcassets */, 66 | 87C4E79828DE0B79005955DA /* LaunchScreen.storyboard */, 67 | 87C4E79B28DE0B79005955DA /* Info.plist */, 68 | 87C4E7A128DE21F2005955DA /* RippleScrollBar.swift */, 69 | ); 70 | path = RippleScrollBarDemo; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | 87C4E78928DE0B78005955DA /* RippleScrollBarDemo */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = 87C4E79E28DE0B79005955DA /* Build configuration list for PBXNativeTarget "RippleScrollBarDemo" */; 79 | buildPhases = ( 80 | 87C4E78628DE0B78005955DA /* Sources */, 81 | 87C4E78728DE0B78005955DA /* Frameworks */, 82 | 87C4E78828DE0B78005955DA /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = RippleScrollBarDemo; 89 | productName = RippleScrollBarDemo; 90 | productReference = 87C4E78A28DE0B78005955DA /* RippleScrollBarDemo.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 87C4E78228DE0B78005955DA /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | BuildIndependentTargetsInParallel = 1; 100 | LastSwiftUpdateCheck = 1400; 101 | LastUpgradeCheck = 1400; 102 | TargetAttributes = { 103 | 87C4E78928DE0B78005955DA = { 104 | CreatedOnToolsVersion = 14.0; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = 87C4E78528DE0B78005955DA /* Build configuration list for PBXProject "RippleScrollBarDemo" */; 109 | compatibilityVersion = "Xcode 14.0"; 110 | developmentRegion = en; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = 87C4E78128DE0B78005955DA; 117 | productRefGroup = 87C4E78B28DE0B78005955DA /* Products */; 118 | projectDirPath = ""; 119 | projectRoot = ""; 120 | targets = ( 121 | 87C4E78928DE0B78005955DA /* RippleScrollBarDemo */, 122 | ); 123 | }; 124 | /* End PBXProject section */ 125 | 126 | /* Begin PBXResourcesBuildPhase section */ 127 | 87C4E78828DE0B78005955DA /* Resources */ = { 128 | isa = PBXResourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | 87C4E79A28DE0B79005955DA /* LaunchScreen.storyboard in Resources */, 132 | 87C4E79728DE0B79005955DA /* Assets.xcassets in Resources */, 133 | 87C4E79528DE0B78005955DA /* Main.storyboard in Resources */, 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXResourcesBuildPhase section */ 138 | 139 | /* Begin PBXSourcesBuildPhase section */ 140 | 87C4E78628DE0B78005955DA /* Sources */ = { 141 | isa = PBXSourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 87C4E7A228DE21F2005955DA /* RippleScrollBar.swift in Sources */, 145 | 87C4E79228DE0B78005955DA /* ViewController.swift in Sources */, 146 | 87C4E78E28DE0B78005955DA /* AppDelegate.swift in Sources */, 147 | 87C4E79028DE0B78005955DA /* SceneDelegate.swift in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin PBXVariantGroup section */ 154 | 87C4E79328DE0B78005955DA /* Main.storyboard */ = { 155 | isa = PBXVariantGroup; 156 | children = ( 157 | 87C4E79428DE0B78005955DA /* Base */, 158 | ); 159 | name = Main.storyboard; 160 | sourceTree = ""; 161 | }; 162 | 87C4E79828DE0B79005955DA /* LaunchScreen.storyboard */ = { 163 | isa = PBXVariantGroup; 164 | children = ( 165 | 87C4E79928DE0B79005955DA /* Base */, 166 | ); 167 | name = LaunchScreen.storyboard; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXVariantGroup section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | 87C4E79C28DE0B79005955DA /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | ALWAYS_SEARCH_USER_PATHS = NO; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 180 | CLANG_ENABLE_MODULES = YES; 181 | CLANG_ENABLE_OBJC_ARC = YES; 182 | CLANG_ENABLE_OBJC_WEAK = YES; 183 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 184 | CLANG_WARN_BOOL_CONVERSION = YES; 185 | CLANG_WARN_COMMA = YES; 186 | CLANG_WARN_CONSTANT_CONVERSION = YES; 187 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 189 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 190 | CLANG_WARN_EMPTY_BODY = YES; 191 | CLANG_WARN_ENUM_CONVERSION = YES; 192 | CLANG_WARN_INFINITE_RECURSION = YES; 193 | CLANG_WARN_INT_CONVERSION = YES; 194 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 195 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 196 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 197 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 198 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 199 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 200 | CLANG_WARN_STRICT_PROTOTYPES = YES; 201 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 202 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 203 | CLANG_WARN_UNREACHABLE_CODE = YES; 204 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 205 | COPY_PHASE_STRIP = NO; 206 | DEBUG_INFORMATION_FORMAT = dwarf; 207 | ENABLE_STRICT_OBJC_MSGSEND = YES; 208 | ENABLE_TESTABILITY = YES; 209 | GCC_C_LANGUAGE_STANDARD = gnu11; 210 | GCC_DYNAMIC_NO_PIC = NO; 211 | GCC_NO_COMMON_BLOCKS = YES; 212 | GCC_OPTIMIZATION_LEVEL = 0; 213 | GCC_PREPROCESSOR_DEFINITIONS = ( 214 | "DEBUG=1", 215 | "$(inherited)", 216 | ); 217 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 218 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 219 | GCC_WARN_UNDECLARED_SELECTOR = YES; 220 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 221 | GCC_WARN_UNUSED_FUNCTION = YES; 222 | GCC_WARN_UNUSED_VARIABLE = YES; 223 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 224 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 225 | MTL_FAST_MATH = YES; 226 | ONLY_ACTIVE_ARCH = YES; 227 | SDKROOT = iphoneos; 228 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 229 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 230 | }; 231 | name = Debug; 232 | }; 233 | 87C4E79D28DE0B79005955DA /* Release */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | CLANG_ANALYZER_NONNULL = YES; 238 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 240 | CLANG_ENABLE_MODULES = YES; 241 | CLANG_ENABLE_OBJC_ARC = YES; 242 | CLANG_ENABLE_OBJC_WEAK = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 250 | CLANG_WARN_EMPTY_BODY = YES; 251 | CLANG_WARN_ENUM_CONVERSION = YES; 252 | CLANG_WARN_INFINITE_RECURSION = YES; 253 | CLANG_WARN_INT_CONVERSION = YES; 254 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 255 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 256 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 258 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 267 | ENABLE_NS_ASSERTIONS = NO; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu11; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 278 | MTL_ENABLE_DEBUG_INFO = NO; 279 | MTL_FAST_MATH = YES; 280 | SDKROOT = iphoneos; 281 | SWIFT_COMPILATION_MODE = wholemodule; 282 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 283 | VALIDATE_PRODUCT = YES; 284 | }; 285 | name = Release; 286 | }; 287 | 87C4E79F28DE0B79005955DA /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 291 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 292 | CODE_SIGN_STYLE = Automatic; 293 | CURRENT_PROJECT_VERSION = 1; 294 | DEVELOPMENT_TEAM = UGULN3J6K6; 295 | GENERATE_INFOPLIST_FILE = YES; 296 | INFOPLIST_FILE = RippleScrollBarDemo/Info.plist; 297 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 298 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 299 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 300 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 301 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 302 | LD_RUNPATH_SEARCH_PATHS = ( 303 | "$(inherited)", 304 | "@executable_path/Frameworks", 305 | ); 306 | MARKETING_VERSION = 1.0; 307 | PRODUCT_BUNDLE_IDENTIFIER = com.sample.RippleScrollBarDemo; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_EMIT_LOC_STRINGS = YES; 310 | SWIFT_VERSION = 5.0; 311 | TARGETED_DEVICE_FAMILY = "1,2"; 312 | }; 313 | name = Debug; 314 | }; 315 | 87C4E7A028DE0B79005955DA /* Release */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 320 | CODE_SIGN_STYLE = Automatic; 321 | CURRENT_PROJECT_VERSION = 1; 322 | DEVELOPMENT_TEAM = UGULN3J6K6; 323 | GENERATE_INFOPLIST_FILE = YES; 324 | INFOPLIST_FILE = RippleScrollBarDemo/Info.plist; 325 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 326 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 327 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 328 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 329 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 330 | LD_RUNPATH_SEARCH_PATHS = ( 331 | "$(inherited)", 332 | "@executable_path/Frameworks", 333 | ); 334 | MARKETING_VERSION = 1.0; 335 | PRODUCT_BUNDLE_IDENTIFIER = com.sample.RippleScrollBarDemo; 336 | PRODUCT_NAME = "$(TARGET_NAME)"; 337 | SWIFT_EMIT_LOC_STRINGS = YES; 338 | SWIFT_VERSION = 5.0; 339 | TARGETED_DEVICE_FAMILY = "1,2"; 340 | }; 341 | name = Release; 342 | }; 343 | /* End XCBuildConfiguration section */ 344 | 345 | /* Begin XCConfigurationList section */ 346 | 87C4E78528DE0B78005955DA /* Build configuration list for PBXProject "RippleScrollBarDemo" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | 87C4E79C28DE0B79005955DA /* Debug */, 350 | 87C4E79D28DE0B79005955DA /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | 87C4E79E28DE0B79005955DA /* Build configuration list for PBXNativeTarget "RippleScrollBarDemo" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | 87C4E79F28DE0B79005955DA /* Debug */, 359 | 87C4E7A028DE0B79005955DA /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | /* End XCConfigurationList section */ 365 | }; 366 | rootObject = 87C4E78228DE0B78005955DA /* Project object */; 367 | } 368 | --------------------------------------------------------------------------------