├── MindMap.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── alina.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── MindMap.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── alina.xcuserdatad │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── MindMap ├── AppDelegate.swift ├── Common │ ├── Extensions │ │ ├── ErrorReporting.swift │ │ ├── String+Extensions.swift │ │ ├── UIApplication+Extensions.swift │ │ ├── UIColor+Extensions.swift │ │ ├── UILabel+Extensions.swift │ │ └── UIVIew+Extensions.swift │ └── UIKit │ │ └── NodeCustomPanGesture.swift ├── Controller │ ├── HomeViewController.swift │ ├── MapViewController.swift │ └── SearchViewController.swift ├── Model │ ├── MapFile.swift │ └── Node.swift ├── Resources │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── arrow.imageset │ │ │ ├── Contents.json │ │ │ └── right-drawn-arrow.png │ │ ├── close.imageset │ │ │ ├── Contents.json │ │ │ └── close.png │ │ ├── folder.imageset │ │ │ ├── Contents.json │ │ │ └── folder.png │ │ ├── idea.imageset │ │ │ ├── Contents.json │ │ │ └── idea.pdf │ │ ├── menu.imageset │ │ │ ├── Contents.json │ │ │ └── menu.png │ │ ├── mindMapImage.imageset │ │ │ ├── Contents.json │ │ │ └── undraw_mind_map_re_nlb6-2 1.pdf │ │ └── send.imageset │ │ │ ├── Contents.json │ │ │ └── plus.png │ ├── Info.plist │ └── icon-doc.png ├── SceneDelegate.swift ├── Services │ └── Storage │ │ └── FileStorage.swift └── View │ ├── AlertView.swift │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── CustomTextField.swift │ ├── Home.storyboard │ ├── HomePageCollectionViewCell │ ├── FileCollectionViewCell.swift │ └── HeaderCollectionView.swift │ ├── MapScrollView.swift │ ├── NodeView.swift │ └── SplineView.swift ├── Podfile ├── Podfile.lock ├── Pods ├── IQKeyboardManagerSwift │ ├── IQKeyboardManagerSwift │ │ ├── Categories │ │ │ ├── IQNSArray+Sort.swift │ │ │ ├── IQUIScrollView+Additions.swift │ │ │ ├── IQUITextFieldView+Additions.swift │ │ │ ├── IQUIView+Hierarchy.swift │ │ │ └── IQUIViewController+Additions.swift │ │ ├── Constants │ │ │ ├── IQKeyboardManagerConstants.swift │ │ │ └── IQKeyboardManagerConstantsInternal.swift │ │ ├── IQKeyboardManager.swift │ │ ├── IQKeyboardReturnKeyHandler.swift │ │ ├── IQTextView │ │ │ └── IQTextView.swift │ │ └── IQToolbar │ │ │ ├── IQBarButtonItem.swift │ │ │ ├── IQInvocation.swift │ │ │ ├── IQPreviousNextView.swift │ │ │ ├── IQTitleBarButtonItem.swift │ │ │ ├── IQToolbar.swift │ │ │ └── IQUIView+IQKeyboardToolbar.swift │ ├── LICENSE.md │ └── README.md ├── Manifest.lock ├── Pods.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ └── alina.xcuserdatad │ │ └── xcschemes │ │ ├── IQKeyboardManagerSwift.xcscheme │ │ ├── Pods-MindMap.xcscheme │ │ └── xcschememanagement.plist └── Target Support Files │ ├── IQKeyboardManagerSwift │ ├── IQKeyboardManagerSwift-Info.plist │ ├── IQKeyboardManagerSwift-dummy.m │ ├── IQKeyboardManagerSwift-prefix.pch │ ├── IQKeyboardManagerSwift-umbrella.h │ ├── IQKeyboardManagerSwift.debug.xcconfig │ ├── IQKeyboardManagerSwift.modulemap │ └── IQKeyboardManagerSwift.release.xcconfig │ └── Pods-MindMap │ ├── Pods-MindMap-Info.plist │ ├── Pods-MindMap-acknowledgements.markdown │ ├── Pods-MindMap-acknowledgements.plist │ ├── Pods-MindMap-dummy.m │ ├── Pods-MindMap-frameworks-Debug-input-files.xcfilelist │ ├── Pods-MindMap-frameworks-Debug-output-files.xcfilelist │ ├── Pods-MindMap-frameworks-Release-input-files.xcfilelist │ ├── Pods-MindMap-frameworks-Release-output-files.xcfilelist │ ├── Pods-MindMap-frameworks.sh │ ├── Pods-MindMap-umbrella.h │ ├── Pods-MindMap.debug.xcconfig │ ├── Pods-MindMap.modulemap │ └── Pods-MindMap.release.xcconfig └── README.md /MindMap.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MindMap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MindMap.xcodeproj/xcuserdata/alina.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MindMap.xcodeproj/xcuserdata/alina.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MindMap.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MindMap.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MindMap.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MindMap.xcworkspace/xcuserdata/alina.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /MindMap/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import IQKeyboardManagerSwift 3 | 4 | @main 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 8 | 9 | // configuring transparent nav bar 10 | UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default) 11 | UINavigationBar.appearance().shadowImage = UIImage() 12 | UINavigationBar.appearance().backgroundColor = .clear 13 | UINavigationBar.appearance().isTranslucent = true 14 | 15 | UIBarButtonItem.appearance().tintColor = .white 16 | UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white] 17 | 18 | 19 | IQKeyboardManager.shared.enable = true 20 | 21 | return true 22 | } 23 | 24 | // MARK: UISceneSession Lifecycle 25 | 26 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 30 | } 31 | 32 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 33 | // Called when the user discards a scene session. 34 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 35 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /MindMap/Common/Extensions/ErrorReporting.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ErrorReporting { 4 | 5 | static func showMessage(title: String, message: String) { 6 | DispatchQueue.main.async { 7 | let errorAlert = UIAlertController(title: "Error", message: message, preferredStyle: UIAlertController.Style.alert) 8 | errorAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) 9 | UIApplication.topViewController()?.present(errorAlert, animated: true, completion: nil) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MindMap/Common/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | static let isFileLocked = "isFileLocked" 5 | 6 | static let pngExtension = ".png" 7 | static let mmdExtension = ".mmd" 8 | } 9 | -------------------------------------------------------------------------------- /MindMap/Common/Extensions/UIApplication+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIApplication { 4 | 5 | static func topViewController(base: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? { 6 | 7 | if let nav = base as? UINavigationController { 8 | return topViewController(base: nav.visibleViewController) 9 | } 10 | 11 | if let tab = base as? UITabBarController, let selected = tab.selectedViewController { 12 | return topViewController(base: selected) 13 | } 14 | 15 | if let presented = base?.presentedViewController { 16 | return topViewController(base: presented) 17 | } 18 | 19 | return base 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MindMap/Common/Extensions/UIColor+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIColor { 4 | static let accentViolet = UIColor(red: 73.0 / 256.0, green: 41.0 / 256.0, blue: 187.0 / 256.0, alpha: 1.0) 5 | static let backgroundViolet = UIColor(red: 28.0 / 256.0, green: 23.0 / 256.0, blue: 46.0 / 256.0, alpha: 1.0) 6 | static let backgroundLight = UIColor(red: 35.0 / 256.0, green: 30.0 / 256.0, blue: 54.0 / 256.0, alpha: 1.0) 7 | static let error = UIColor(red: 238.0 / 256.0, green: 112.0 / 256.0, blue: 157.0 / 256.0, alpha: 1.0) 8 | static let regularLight = UIColor(red: 188.0 / 256.0, green: 184.0 / 256.0, blue: 204.0 / 256.0, alpha: 1.0) 9 | 10 | 11 | func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { 12 | return UIGraphicsImageRenderer(size: size).image { rendererContext in 13 | self.setFill() 14 | rendererContext.fill(CGRect(origin: .zero, size: size)) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MindMap/Common/Extensions/UILabel+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UITextView { 4 | func getSize(constrainedWidth: CGFloat) -> CGSize { 5 | return systemLayoutSizeFitting(CGSize(width: constrainedWidth, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MindMap/Common/Extensions/UIVIew+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIView { 4 | func rotate() { 5 | let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") 6 | rotation.toValue = NSNumber(value: Double.pi) 7 | rotation.duration = 0.3 8 | rotation.isCumulative = true 9 | rotation.repeatCount = 1 10 | self.layer.add(rotation, forKey: "rotationAnimation") 11 | } 12 | 13 | func screenshot() -> UIImage { 14 | return UIGraphicsImageRenderer(size: bounds.size).image { _ in 15 | drawHierarchy(in: CGRect(origin: .zero, size: bounds.size), afterScreenUpdates: true) 16 | } 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /MindMap/Common/UIKit/NodeCustomPanGesture.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class NodeCustomPanGesture: UIPanGestureRecognizer { 4 | var node: Node? 5 | var nodeView: NodeView? 6 | } 7 | -------------------------------------------------------------------------------- /MindMap/Controller/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LocalAuthentication 3 | 4 | class HomeViewController: UIViewController { 5 | // MARK: Stored properties 6 | 7 | var sections = Section.allCases 8 | private let fileStorage = FileStorage() 9 | 10 | // MARK: Outlet properties 11 | let alert = AlertView(frame: .zero) 12 | lazy var searchBar = UISearchBar(frame: .zero) 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | @IBOutlet weak var arrowImage: UIImageView! 17 | @IBOutlet weak var logoImage: UIImageView! 18 | 19 | var recentFiles = [MapFile]() 20 | var allFiles = [MapFile]() 21 | 22 | // MARK: Lifecycle 23 | override func viewWillAppear(_ animated: Bool) { 24 | super.viewWillAppear(animated) 25 | 26 | configureUI() 27 | } 28 | 29 | func configureUI() { 30 | reloadData() 31 | 32 | searchBar.delegate = self 33 | let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField 34 | textFieldInsideSearchBar?.textColor = .white 35 | textFieldInsideSearchBar?.leftView?.tintColor = .white 36 | 37 | searchBar.tintColor = .white 38 | 39 | if allFiles.isEmpty { 40 | collectionView.isHidden = true 41 | logoImage.isHidden = false 42 | arrowImage.isHidden = false 43 | navigationItem.titleView = nil 44 | } else { 45 | // configuring searchBar 46 | searchBar.placeholder = "Search" 47 | searchBar.sizeToFit() 48 | navigationItem.titleView = searchBar 49 | collectionView.isHidden = false 50 | logoImage.isHidden = true 51 | arrowImage.isHidden = true 52 | } 53 | } 54 | 55 | func reloadData() { 56 | // fetch all files 57 | allFiles = fetchAllDocs() 58 | 59 | //fetch recent files 60 | recentFiles = fetchRecentDocs() 61 | 62 | collectionView.reloadData() 63 | } 64 | 65 | func fetchAllDocs() -> [MapFile] { 66 | var mapFiles = [MapFile]() 67 | let filesPath = fileStorage.getAllFiles(with: String.mmdExtension) 68 | filesPath.forEach { path in 69 | do { 70 | let encodedMapFile = try self.fileStorage.getFile(atPath: path) 71 | let mapData = try JSONDecoder().decode(MapFile.self, from: encodedMapFile) 72 | if let imageData = try? fileStorage.getFile(atPath: "\(mapData.rootNode.name)\(String.pngExtension)"), let image = UIImage(data: imageData, scale: 1.0) { 73 | mapFiles.append(MapFile(image: image, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: mapData.state)) 74 | } else { 75 | mapFiles.append(MapFile(image: UIImage(named: "folder")!, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: mapData.state)) 76 | } 77 | } catch { 78 | print(error) 79 | } 80 | } 81 | return mapFiles 82 | } 83 | 84 | func fetchRecentDocs() -> [MapFile] { 85 | var mapFiles = [MapFile]() 86 | let filesPath = fileStorage.getRecentFiles(with: String.mmdExtension).prefix(4) 87 | filesPath.forEach { path in 88 | do { 89 | let encodedMapFile = try self.fileStorage.getFile(atPath: path) 90 | let mapData = try JSONDecoder().decode(MapFile.self, from: encodedMapFile) 91 | if let imageData = try? fileStorage.getFile(atPath: "\(mapData.rootNode.name)\(String.pngExtension)"), let image = UIImage(data: imageData, scale: 1.0) { 92 | mapFiles.append(MapFile(image: image, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: mapData.state)) 93 | } else { 94 | mapFiles.append(MapFile(image: UIImage(named: "folder")!, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: mapData.state)) 95 | } 96 | } catch { 97 | print(error) 98 | } 99 | } 100 | return mapFiles 101 | } 102 | 103 | func openMap(mapFile: MapFile) { 104 | guard let mapVC = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "MapViewController") as? MapViewController else { return } 105 | let state = mapFile.state 106 | mapVC.mapFile = mapFile 107 | mapVC.state = state 108 | 109 | if state == .locked { 110 | let context = LAContext() 111 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Protect with Touch ID") { success, error in 112 | if success { 113 | //show locked file 114 | DispatchQueue.main.async { 115 | self.navigationController?.pushViewController(mapVC, animated: true) 116 | } 117 | } else { 118 | ErrorReporting.showMessage(title: "Error", message: "This file is private! ❌") 119 | } 120 | } 121 | } else { 122 | DispatchQueue.main.async { 123 | self.navigationController?.pushViewController(mapVC, animated: true) 124 | } 125 | } 126 | } 127 | 128 | // MARK: Functions 129 | @IBAction private func addButtonDidTap(_ sender: Any) { 130 | arrowImage.isHidden = true 131 | // configure alert 132 | alert.translatesAutoresizingMaskIntoConstraints = false 133 | alert.delegate = self 134 | 135 | view.addSubview(alert) 136 | alert.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 137 | alert.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 138 | alert.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 139 | alert.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 140 | 141 | navigationController?.setNavigationBarHidden(true, animated: true) 142 | } 143 | 144 | // MARK: CollectionView Helpers 145 | enum Section: Int, CaseIterable { 146 | case recent 147 | case all 148 | } 149 | 150 | func sectionAt(_ indexPath: Int) -> Section { 151 | return sections[indexPath] 152 | } 153 | 154 | private func indexPath(for section: Section) -> IndexPath? { 155 | return IndexPath(row: section.rawValue, section: 0) 156 | } 157 | 158 | func reloadRow(_ section: Section) { 159 | guard let indexPath = self.indexPath(for: section) else { return } 160 | self.collectionView.reloadSections([indexPath.section]) 161 | } 162 | } 163 | 164 | //MARK: - AlertView Delegate 165 | extension HomeViewController: AlertViewDelegate { 166 | 167 | func closeAlert() { 168 | alert.removeFromSuperview() 169 | navigationController?.setNavigationBarHidden(false, animated: true) 170 | } 171 | 172 | func addNode(name: String) { 173 | // presenting Map View Controller 174 | closeAlert() 175 | guard let mapVC = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "MapViewController") as? MapViewController else { return } 176 | mapVC.rootNodeName = name 177 | mapVC.state = .regular 178 | self.navigationController?.pushViewController(mapVC, animated: true) 179 | } 180 | 181 | } 182 | 183 | //MARK: - UICollectionViewDelegate 184 | extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { 185 | 186 | func numberOfSections(in collectionView: UICollectionView) -> Int { 187 | return sections.count 188 | } 189 | 190 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 191 | return sectionAt(section) == .all ? allFiles.count : recentFiles.count 192 | } 193 | 194 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 195 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FileCollectionViewCell",for: indexPath) as? FileCollectionViewCell else { 196 | return UICollectionViewCell() 197 | } 198 | 199 | cell.delegate = self 200 | switch sectionAt(indexPath.section) { 201 | case .all: 202 | cell.configureUI(name: allFiles[indexPath.item].rootNode.name, image: allFiles[indexPath.item].image, state: allFiles[indexPath.item].state) 203 | case .recent: 204 | cell.configureUI(name: recentFiles[indexPath.item].rootNode.name, image: recentFiles[indexPath.item].image, state: recentFiles[indexPath.item].state) 205 | } 206 | 207 | return cell 208 | } 209 | 210 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 211 | let noOfCellsInRow = UIDevice.current.userInterfaceIdiom == .pad ? 4 : 2 212 | let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout 213 | let totalSpace = flowLayout.sectionInset.left + flowLayout.sectionInset.right + (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1)) 214 | let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow)) 215 | 216 | return CGSize(width: size, height: size * 2 / 3) 217 | } 218 | 219 | // sections inset 220 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 221 | return UIEdgeInsets(top: 0, left: 0, bottom: sectionAt(section) == .recent ? 64 : 0, right: 0) 222 | } 223 | 224 | // configure header view 225 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 226 | if kind == UICollectionView.elementKindSectionHeader { 227 | let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HCollectionReusableView", for: indexPath) as! HeaderCollectionView 228 | header.configure(title: sectionAt(indexPath.section) == .recent ? "Recent" : "All") 229 | return header 230 | } else { 231 | return UICollectionReusableView() 232 | } 233 | } 234 | 235 | // configure header height 236 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { 237 | return CGSize(width: collectionView.frame.width, height: 50) 238 | } 239 | 240 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 241 | let mapFile = sectionAt(indexPath.section) == .all ? allFiles[indexPath.item] : recentFiles[indexPath.item] 242 | openMap(mapFile: mapFile) 243 | } 244 | } 245 | 246 | //MARK: - FileCollectionViewCellDelegate 247 | extension HomeViewController: FileCollectionViewCellDelegate { 248 | func deleteMap(with name: String) { 249 | do { 250 | try fileStorage.deleteFile(atPath: "\(name)\(String.pngExtension)") 251 | try fileStorage.deleteFile(atPath: "\(name)\(String.mmdExtension)") 252 | configureUI() 253 | } catch { 254 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to delete your map!") 255 | } 256 | } 257 | 258 | func lockMap(state: MapState, with name: String) { 259 | let context = LAContext() 260 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Protect with Touch ID") { success, error in 261 | if success { 262 | //lock file 263 | do { 264 | // getting old file 265 | let encodedMapFile = try self.fileStorage.getFile(atPath: "\(name)\(String.mmdExtension)") 266 | let mapData = try JSONDecoder().decode(MapFile.self, from: encodedMapFile) 267 | 268 | // re-writing old file with a new state 269 | let mapFile = MapFile(image: mapData.image, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: state) 270 | let encodedFile = try JSONEncoder().encode(mapFile) 271 | try self.fileStorage.writeFile(encodedFile, atPath: "\(name)\(String.mmdExtension)") 272 | 273 | DispatchQueue.main.async { 274 | self.reloadData() 275 | } 276 | } catch { 277 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to lock your map!") 278 | } 279 | } else { 280 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to lock your map!") 281 | } 282 | } 283 | } 284 | } 285 | 286 | //MARK: - UISearchBarDelegate 287 | extension HomeViewController: UISearchBarDelegate { 288 | func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { 289 | searchBar.resignFirstResponder() 290 | 291 | guard let searchVC = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "SearchViewController") as? SearchViewController else { 292 | return 293 | } 294 | self.navigationController?.pushViewController(searchVC, animated: true) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /MindMap/Controller/MapViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LocalAuthentication 3 | 4 | class MapViewController: UIViewController { 5 | 6 | // MARK: Stored properties 7 | var rootNodeName: String? 8 | 9 | var mapFile: MapFile? 10 | var state: MapState? 11 | var deleteMap = false 12 | 13 | private let fileStorage = FileStorage() 14 | 15 | // MARK: Outlet properties 16 | @IBOutlet weak var shareButton: UIBarButtonItem! 17 | @IBOutlet weak var lockButton: UIBarButtonItem! 18 | 19 | var mapScrollView: MapScrollView! 20 | 21 | // MARK: Lifecycle 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | mapScrollView = MapScrollView(frame: view.bounds) 26 | view.addSubview(mapScrollView) 27 | setupMapScrollView() 28 | mapScrollView.mapDelegate = self 29 | view.backgroundColor = UIColor.backgroundLight 30 | configureLockButton() 31 | 32 | if let rootNodeName = rootNodeName { 33 | //create new map 34 | mapScrollView.configureUI(rootNodeName: rootNodeName) 35 | } else if let mapFile = mapFile { 36 | //open old map 37 | let size = CGSize(width: mapFile.contentViewSize.width, height: mapFile.contentViewSize.height) 38 | mapScrollView.configureUI(viewSize: size, mapFile: mapFile) 39 | UserDefaults.standard.set(mapFile.state == .locked ? true : false, forKey: String.isFileLocked) 40 | } 41 | } 42 | 43 | func setupMapScrollView() { 44 | mapScrollView.translatesAutoresizingMaskIntoConstraints = false 45 | mapScrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 46 | mapScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 47 | mapScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 48 | mapScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 49 | } 50 | 51 | override func viewWillAppear(_ animated: Bool) { 52 | super.viewWillAppear(animated) 53 | 54 | self.navigationController?.navigationBar.topItem?.title = " " 55 | } 56 | 57 | override func viewWillDisappear(_ animated: Bool) { 58 | super.viewWillDisappear(animated) 59 | 60 | UserDefaults.standard.set(false, forKey: String.isFileLocked) 61 | 62 | if self.isMovingFromParent, deleteMap == false { 63 | self.mapScrollView.saveMap() 64 | } 65 | } 66 | 67 | @IBAction func exportButtonDidTap(_ sender: Any) { 68 | if let rootNodeName = mapScrollView.rootNode?.name { 69 | // adding file to share 70 | var filesToShare = [Any]() 71 | filesToShare.append(fileStorage.documentDirectoryPath("\(rootNodeName)\(String.mmdExtension)")) 72 | 73 | let activity = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil) 74 | activity.excludedActivityTypes = [.airDrop] 75 | if UIDevice.current.userInterfaceIdiom == .pad { 76 | activity.modalPresentationStyle = .popover 77 | activity.popoverPresentationController?.barButtonItem = shareButton 78 | } 79 | self.present(activity, animated: true, completion: nil) 80 | } 81 | } 82 | 83 | @IBAction func lockButtonDidTap(_ sender: Any) { 84 | let context = LAContext() 85 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Protect with Touch ID") { success, error in 86 | if success { 87 | //lock file 88 | DispatchQueue.main.async { 89 | self.state = self.state == .regular ? .locked : .regular 90 | self.mapScrollView.state = self.state 91 | self.mapScrollView.saveMap() 92 | UserDefaults.standard.set(self.state == .locked ? true : false, forKey: String.isFileLocked) 93 | self.configureLockButton() 94 | } 95 | } else { 96 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to lock your map!") 97 | } 98 | } 99 | } 100 | 101 | func configureLockButton() { 102 | lockButton.image = UIImage(systemName: state == .regular ? "lock.open" : "lock") 103 | } 104 | 105 | } 106 | 107 | extension MapViewController: MapScrollViewDelegate { 108 | func presentAlert(mapFile: MapFile) { 109 | let alert = UIAlertController(title: "Do you want to delete map? 🤯", message: nil, preferredStyle: .alert) 110 | alert.addAction(UIAlertAction(title: "Yes", style: .destructive, handler: { _ in 111 | self.deleteMap = true 112 | let rootName = mapFile.rootNode.name 113 | do { 114 | try self.fileStorage.deleteFile(atPath: "\(rootName)\(String.pngExtension)") 115 | try self.fileStorage.deleteFile(atPath: "\(rootName)\(String.mmdExtension)") 116 | self.navigationController?.popViewController(animated: true) 117 | } catch { 118 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to delete your map!") 119 | } 120 | })) 121 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 122 | self.present(alert, animated: true) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /MindMap/Controller/SearchViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LocalAuthentication 3 | 4 | class SearchViewController: UIViewController { 5 | 6 | // MARK: Stored properties 7 | private let fileStorage = FileStorage() 8 | var searchedFiles = [MapFile]() 9 | 10 | lazy var searchBar = UISearchBar(frame: .zero) 11 | 12 | // MARK: Outlet properties 13 | @IBOutlet weak var collectionView: UICollectionView! 14 | @IBOutlet weak var noResultsLabel: UILabel! 15 | 16 | // MARK: Lifecycle 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | configureUI() 21 | } 22 | 23 | override func viewWillAppear(_ animated: Bool) { 24 | super.viewWillAppear(animated) 25 | 26 | self.navigationController?.navigationBar.topItem?.title = " " 27 | } 28 | 29 | func configureUI() { 30 | navigationItem.titleView = searchBar 31 | searchBar.delegate = self 32 | let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField 33 | textFieldInsideSearchBar?.textColor = .white 34 | textFieldInsideSearchBar?.leftView?.tintColor = .white 35 | searchBar.becomeFirstResponder() 36 | } 37 | 38 | func reloadData() { 39 | //fetch searched files 40 | if let searchText = searchBar.text { 41 | searchedFiles = fetchSearchedDocuments(search: searchText) 42 | noResultsLabel.isHidden = !searchedFiles.isEmpty 43 | collectionView.reloadData() 44 | } 45 | } 46 | 47 | func fetchSearchedDocuments(search: String) -> [MapFile] { 48 | var mapFiles = [MapFile]() 49 | let filesPath = fileStorage.getAllFiles(with: String.mmdExtension) 50 | filesPath.filter({$0.contains("\(search)")}).forEach { path in 51 | do { 52 | let encodedMapFile = try self.fileStorage.getFile(atPath: path) 53 | let mapData = try JSONDecoder().decode(MapFile.self, from: encodedMapFile) 54 | let imageData = try fileStorage.getFile(atPath: "\(mapData.rootNode.name)\(String.pngExtension)") 55 | if let image = UIImage(data: imageData, scale: 1.0) { 56 | mapFiles.append(MapFile(image: image, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: mapData.state)) 57 | } else { 58 | mapFiles.append(MapFile(image: UIImage(named: "folder")!, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: mapData.state)) 59 | } 60 | } catch { 61 | print(error) 62 | } 63 | } 64 | return mapFiles 65 | } 66 | 67 | } 68 | 69 | //MARK: - UICollectionView Delegate 70 | extension SearchViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { 71 | 72 | func numberOfSections(in collectionView: UICollectionView) -> Int { 73 | return 1 74 | } 75 | 76 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 77 | return searchedFiles.count 78 | } 79 | 80 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 81 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FileCollectionViewCell",for: indexPath) as? FileCollectionViewCell else { 82 | return UICollectionViewCell() 83 | } 84 | cell.delegate = self 85 | cell.configureUI(name: searchedFiles[indexPath.item].rootNode.name, image: searchedFiles[indexPath.item].image, state: searchedFiles[indexPath.item].state) 86 | return cell 87 | } 88 | 89 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 90 | let noOfCellsInRow = UIDevice.current.userInterfaceIdiom == .pad ? 4 : 2 91 | let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout 92 | let totalSpace = flowLayout.sectionInset.left + flowLayout.sectionInset.right + (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1)) 93 | let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow)) 94 | 95 | return CGSize(width: size, height: size * 2 / 3) 96 | } 97 | 98 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 99 | guard let mapVC = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "MapViewController") as? MapViewController else { 100 | return 101 | } 102 | 103 | let state = searchedFiles[indexPath.item].state 104 | 105 | mapVC.mapFile = searchedFiles[indexPath.item] 106 | mapVC.state = state 107 | 108 | if state == .locked { 109 | let context = LAContext() 110 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Protect with Touch ID") { success, error in 111 | if success { 112 | //show locked file 113 | DispatchQueue.main.async { 114 | self.navigationController?.pushViewController(mapVC, animated: true) 115 | } 116 | } else { 117 | ErrorReporting.showMessage(title: "Error", message: "This file is private! ❌") 118 | } 119 | } 120 | } else { 121 | self.navigationController?.pushViewController(mapVC, animated: true) 122 | } 123 | } 124 | } 125 | 126 | //MARK: - FileCollectionViewCellDelegate 127 | extension SearchViewController: FileCollectionViewCellDelegate { 128 | func deleteMap(with name: String) { 129 | do { 130 | try fileStorage.deleteFile(atPath: "\(name)\(String.pngExtension)") 131 | try fileStorage.deleteFile(atPath: "\(name)\(String.mmdExtension)") 132 | reloadData() 133 | } catch { 134 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to delete your map!") 135 | } 136 | 137 | } 138 | 139 | func lockMap(state: MapState, with name: String) { 140 | let context = LAContext() 141 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Protect with Touch ID") { success, error in 142 | if success { 143 | //lock file 144 | do { 145 | // getting old file 146 | let encodedMapFile = try self.fileStorage.getFile(atPath: "\(name)\(String.mmdExtension)") 147 | let mapData = try JSONDecoder().decode(MapFile.self, from: encodedMapFile) 148 | 149 | // re-writing old file with a new state 150 | let mapFile = MapFile(image: mapData.image, rootNode: mapData.rootNode, contentViewSize: mapData.contentViewSize, state: state) 151 | let encodedFile = try JSONEncoder().encode(mapFile) 152 | try self.fileStorage.writeFile(encodedFile, atPath: "\(name)\(String.mmdExtension)") 153 | 154 | DispatchQueue.main.async { 155 | self.reloadData() 156 | } 157 | } catch { 158 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to lock your map!") 159 | } 160 | } else { 161 | ErrorReporting.showMessage(title: "Error", message: "Wasn't able to lock your map!") 162 | } 163 | } 164 | } 165 | } 166 | 167 | //MARK: - UISearchBarDelegate 168 | extension SearchViewController: UISearchBarDelegate { 169 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 170 | reloadData() 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /MindMap/Model/MapFile.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | struct MapFile: Codable { 4 | var image: UIImage 5 | var rootNode: Node 6 | var contentViewSize: CGSize 7 | var state: MapState 8 | 9 | enum CodingKeys: String, CodingKey { 10 | case image, rootNode, contentViewSize, state 11 | } 12 | 13 | init(image: UIImage, rootNode: Node, contentViewSize: CGSize, state: MapState) { 14 | self.image = image 15 | self.rootNode = rootNode 16 | self.contentViewSize = contentViewSize 17 | self.state = state 18 | } 19 | 20 | init(from decoder: Decoder) throws { 21 | let values = try decoder.container(keyedBy: CodingKeys.self) 22 | 23 | rootNode = try values.decode(Node.self, forKey: .rootNode) 24 | contentViewSize = try values.decode(CGSize.self, forKey: .contentViewSize) 25 | state = try values.decode(MapState.self, forKey: .state) 26 | let data = try values.decode(Data.self, forKey: .image) 27 | guard let image = UIImage(data: data) else { 28 | throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data") 29 | } 30 | self.image = image 31 | } 32 | 33 | func encode(to encoder: Encoder) throws { 34 | var container = encoder.container(keyedBy: CodingKeys.self) 35 | 36 | try container.encode(rootNode, forKey: .rootNode) 37 | try container.encode(contentViewSize, forKey: .contentViewSize) 38 | try container.encode(state, forKey: .state) 39 | try container.encode(image.pngData(), forKey: .image) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MindMap/Model/Node.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class Node: NSObject, Codable { 5 | // MARK: Stored properties 6 | let id = UUID() 7 | var name: String 8 | var centerPosition: CGPoint? 9 | 10 | private (set) var children: [Node] = [] 11 | weak var parent: Node? 12 | 13 | override var description: String { 14 | var text = "\(name)" 15 | if !children.isEmpty { 16 | text += " [" + children.map { $0.description }.joined(separator: ", ") + "] " 17 | } 18 | return text 19 | } 20 | 21 | // MARK: Initialize 22 | init(name: String) { 23 | self.name = name 24 | } 25 | 26 | // MARK: Methods 27 | func add(child: Node) { 28 | children.append(child) 29 | child.parent = self 30 | } 31 | 32 | func search(id: UUID) -> Node? { 33 | if let n = self.children.filter({ $0.id == id}).first { 34 | return n 35 | } 36 | if id == self.id { 37 | return self 38 | } 39 | for child in children { 40 | if let found = child.search(id: id) { 41 | return found 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | func remove(node: Node) { 48 | if let nodeToDelete = search(id: node.id), let parentNodeToDelete = nodeToDelete.parent { 49 | parentNodeToDelete.children.removeAll(where: { childNode in 50 | nodeToDelete == childNode 51 | }) 52 | } 53 | } 54 | 55 | func depthOfNode(id: UUID) -> Int { 56 | var result = 0 57 | var node = search(id: id) 58 | 59 | while node?.parent != nil { 60 | result += 2 61 | node = node?.parent 62 | } 63 | return result 64 | } 65 | 66 | func forEachDepthFirst(visit: (Node) -> Void) { 67 | visit(self) 68 | children.forEach { $0.forEachDepthFirst(visit: visit) } 69 | } 70 | 71 | //MARK: - Codable 72 | private enum CodingKeys: String, CodingKey { 73 | case name 74 | case children 75 | case centerPosition 76 | } 77 | 78 | func encode(to encoder: Encoder) throws { 79 | var container = encoder.container(keyedBy: CodingKeys.self) 80 | try container.encode(name, forKey: .name) 81 | try container.encode(centerPosition, forKey: .centerPosition) 82 | if !children.isEmpty { 83 | try container.encode(children, forKey: .children) 84 | } else { 85 | try container.encode([Node](), forKey: .children) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /MindMap/Resources/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 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "right-drawn-arrow.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/arrow.imageset/right-drawn-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/arrow.imageset/right-drawn-arrow.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "close.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/close.imageset/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/close.imageset/close.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/folder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "folder.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/folder.imageset/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/folder.imageset/folder.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/idea.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "idea.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "menu.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/menu.imageset/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/menu.imageset/menu.png -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/mindMapImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "undraw_mind_map_re_nlb6-2 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/send.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "plus.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MindMap/Resources/Assets.xcassets/send.imageset/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/Assets.xcassets/send.imageset/plus.png -------------------------------------------------------------------------------- /MindMap/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDocumentTypes 6 | 7 | 8 | CFBundleTypeIconFiles 9 | 10 | icon-doc.png 11 | 12 | CFBundleTypeName 13 | TaskList Data 14 | CFBundleTypeRole 15 | Editor 16 | LSHandlerRank 17 | Owner 18 | LSItemContentTypes 19 | 20 | com.alibogzl.MindMap.MindMapData 21 | 22 | 23 | 24 | UIApplicationSceneManifest 25 | 26 | UIApplicationSupportsMultipleScenes 27 | 28 | UISceneConfigurations 29 | 30 | UIWindowSceneSessionRoleApplication 31 | 32 | 33 | UISceneConfigurationName 34 | Default Configuration 35 | UISceneDelegateClassName 36 | $(PRODUCT_MODULE_NAME).SceneDelegate 37 | UISceneStoryboardFile 38 | Home 39 | 40 | 41 | 42 | 43 | UTExportedTypeDeclarations 44 | 45 | 46 | UTTypeConformsTo 47 | 48 | public.data 49 | 50 | UTTypeDescription 51 | MindMap Data 52 | UTTypeIconFiles 53 | 54 | icon-doc.png 55 | 56 | UTTypeIdentifier 57 | com.alibogzl.MindMap.MindMapData 58 | UTTypeTagSpecification 59 | 60 | public.filename-extension 61 | 62 | mmd 63 | 64 | 65 | 66 | 67 | 68 | UTImportedTypeDeclarations 69 | 70 | 71 | UTTypeConformsTo 72 | 73 | public.data 74 | 75 | UTTypeDescription 76 | MindMap Data 77 | UTTypeIconFiles 78 | 79 | icon-doc.png 80 | 81 | UTTypeIdentifier 82 | com.alibogzl.MindMap.MindMapData 83 | UTTypeTagSpecification 84 | 85 | public.filename-extension 86 | 87 | mmd 88 | 89 | 90 | 91 | 92 | LSSupportsOpeningDocumentsInPlace 93 | 94 | UIFileSharingEnabled 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /MindMap/Resources/icon-doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosdev29/MindMap/bdeaf28939a1016a6f88e94bf61b647c3bdea07f/MindMap/Resources/icon-doc.png -------------------------------------------------------------------------------- /MindMap/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LocalAuthentication 3 | 4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { 9 | guard let urlContext = URLContexts.first else { return } 10 | do { 11 | let importedMap = try FileStorage().copyFile(at: urlContext.url) 12 | // present imported content 13 | guard let windowScene = (scene as? UIWindowScene) else { return } 14 | self.window = UIWindow(windowScene: windowScene) 15 | 16 | guard let rootVC = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(identifier: "HomeViewController") as? HomeViewController else { return } 17 | let rootNC = UINavigationController(rootViewController: rootVC) 18 | rootNC.navigationBar.topItem?.title = " " 19 | self.window?.rootViewController = rootNC 20 | self.window?.makeKeyAndVisible() 21 | 22 | guard let mapVC = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "MapViewController") as? MapViewController else { return } 23 | let state = importedMap.state 24 | mapVC.mapFile = importedMap 25 | mapVC.state = state 26 | 27 | if state == .locked { 28 | let context = LAContext() 29 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Protect with Touch ID") { success, error in 30 | if success { 31 | //show locked file 32 | DispatchQueue.main.async { 33 | rootNC.pushViewController(mapVC, animated: true) 34 | } 35 | } else { 36 | ErrorReporting.showMessage(title: "Error", message: "This file is private! ❌") 37 | } 38 | } 39 | } else { 40 | rootNC.pushViewController(mapVC, animated: true) 41 | } 42 | } catch { 43 | ErrorReporting.showMessage(title: "Error", message: "Import unsuccessful") 44 | } 45 | } 46 | 47 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 48 | self.scene(scene, openURLContexts: connectionOptions.urlContexts) 49 | } 50 | 51 | func sceneDidDisconnect(_ scene: UIScene) { 52 | // Called as the scene is being released by the system. 53 | // This occurs shortly after the scene enters the background, or when its session is discarded. 54 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 55 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 56 | } 57 | 58 | func sceneDidBecomeActive(_ scene: UIScene) { 59 | // Called when the scene has moved from an inactive state to an active state. 60 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 61 | hidePrivacyProtectionWindow() 62 | } 63 | 64 | func sceneWillResignActive(_ scene: UIScene) { 65 | // Called when the scene will move from an active state to an inactive state. 66 | // This may occur due to temporary interruptions (ex. an incoming phone call). 67 | if UserDefaults.standard.bool(forKey: String.isFileLocked) { 68 | showPrivacyProtectionWindow() 69 | } 70 | } 71 | 72 | func sceneWillEnterForeground(_ scene: UIScene) { 73 | // Called as the scene transitions from the background to the foreground. 74 | // Use this method to undo the changes made on entering the background. 75 | hidePrivacyProtectionWindow() 76 | } 77 | 78 | func sceneDidEnterBackground(_ scene: UIScene) { 79 | // Called as the scene transitions from the foreground to the background. 80 | // Use this method to save data, release shared resources, and store enough scene-specific state information 81 | // to restore the scene back to its current state. 82 | if UserDefaults.standard.bool(forKey: String.isFileLocked) { 83 | showPrivacyProtectionWindow() 84 | } 85 | } 86 | 87 | // MARK: Privacy Protection 88 | private var privacyProtectionWindow: UIWindow? 89 | 90 | private func showPrivacyProtectionWindow() { 91 | guard let windowScene = self.window?.windowScene else { 92 | return 93 | } 94 | privacyProtectionWindow = UIWindow(windowScene: windowScene) 95 | privacyProtectionWindow?.windowLevel = .alert + 1 96 | privacyProtectionWindow?.makeKeyAndVisible() 97 | 98 | let blurEffect = UIBlurEffect(style: .light) 99 | let blurEffectView = UIVisualEffectView(effect: blurEffect) 100 | blurEffectView.frame = window!.frame 101 | blurEffectView.tag = 1221 102 | privacyProtectionWindow?.addSubview(blurEffectView) 103 | } 104 | 105 | private func hidePrivacyProtectionWindow() { 106 | privacyProtectionWindow?.isHidden = true 107 | privacyProtectionWindow = nil 108 | privacyProtectionWindow?.viewWithTag(1221)?.removeFromSuperview() 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /MindMap/Services/Storage/FileStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class FileStorage { 4 | 5 | // MARK: Stored properties 6 | private let fileManager: FileManager 7 | 8 | // MARK: Initialize 9 | init(fileManager: FileManager = FileManager.default) { 10 | self.fileManager = fileManager 11 | } 12 | 13 | // MARK: Functions 14 | func documentDirectoryPath(_ appendingPath: String) -> URL { 15 | guard let url = self.fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { 16 | fatalError("\(#function) `documentDirectory` not found") 17 | } 18 | return url.appendingPathComponent(appendingPath) 19 | } 20 | 21 | func getFile(atPath path: String) throws -> Data { 22 | let url = self.documentDirectoryPath(path) 23 | return try Data(contentsOf: url, options: .mappedIfSafe) 24 | } 25 | 26 | func writeFile(_ data: Data, atPath path: String) throws { 27 | let url = self.documentDirectoryPath(path) 28 | try data.write(to: url) 29 | } 30 | 31 | func deleteFile(atPath path: String) throws { 32 | let url = self.documentDirectoryPath(path) 33 | try self.fileManager.removeItem(at: url) 34 | } 35 | 36 | func copyFile(at url: URL) throws -> MapFile { 37 | let encodedMapFile = try Data(contentsOf: url, options: .mappedIfSafe) 38 | // import file 39 | let mapData = try JSONDecoder().decode(MapFile.self, from: encodedMapFile) 40 | try writeFile(encodedMapFile, atPath: "\(mapData.rootNode.name)\(String.mmdExtension)") 41 | return mapData 42 | } 43 | 44 | func getAllFiles(with type: String) -> [String] { 45 | let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 46 | if let urlArray = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey], options:.skipsHiddenFiles) { 47 | return urlArray.map { url in 48 | (url.lastPathComponent, (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast) 49 | }.filter({ $0.0.hasSuffix(type)}).map { $0.0 } 50 | } else { 51 | return [String]() 52 | } 53 | } 54 | 55 | func getRecentFiles(with type: String) -> [String] { 56 | let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 57 | if let urlArray = try? FileManager.default.contentsOfDirectory(at: directory,includingPropertiesForKeys: [.contentModificationDateKey], options:.skipsHiddenFiles) { 58 | return urlArray.map { url in 59 | (url.lastPathComponent, (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast) 60 | }.filter({ $0.0.hasSuffix(type)}) 61 | .sorted(by: { $0.1 > $1.1 }) 62 | .map { $0.0 } 63 | } else { 64 | return [String]() 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /MindMap/View/AlertView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol AlertViewDelegate: AnyObject { 4 | func addNode(name: String) 5 | func closeAlert() 6 | } 7 | 8 | class AlertView: UIView { 9 | 10 | let alertHeight = 150.0 11 | 12 | private var backgroundView = { () -> UIView in 13 | let container = UIView() 14 | container.translatesAutoresizingMaskIntoConstraints = false 15 | container.backgroundColor = UIColor.black.withAlphaComponent(0.3) 16 | return container 17 | }() 18 | 19 | private var containerView = { () -> UIView in 20 | let container = UIView() 21 | container.translatesAutoresizingMaskIntoConstraints = false 22 | container.backgroundColor = UIColor.backgroundViolet 23 | container.layer.cornerRadius = 8 24 | return container 25 | }() 26 | 27 | private lazy var stackView = { () -> UIStackView in 28 | let stackView = UIStackView(arrangedSubviews: [textField, addButtonContainer]) 29 | stackView.translatesAutoresizingMaskIntoConstraints = false 30 | stackView.axis = .horizontal 31 | stackView.alignment = .fill 32 | stackView.distribution = .fill 33 | stackView.backgroundColor = .clear 34 | stackView.spacing = 8.0 35 | return stackView 36 | }() 37 | 38 | private var titleLabel = { () -> UILabel in 39 | let label = UILabel() 40 | label.text = "Create your map 💡" 41 | label.numberOfLines = 0 42 | label.lineBreakMode = .byWordWrapping 43 | label.textAlignment = .center 44 | label.textColor = UIColor.white 45 | label.translatesAutoresizingMaskIntoConstraints = false 46 | return label 47 | }() 48 | 49 | private var errorLabel = { () -> UILabel in 50 | let label = UILabel() 51 | label.text = " " 52 | label.textAlignment = .center 53 | label.font = .systemFont(ofSize: 10) 54 | label.textColor = UIColor.error 55 | label.translatesAutoresizingMaskIntoConstraints = false 56 | return label 57 | }() 58 | 59 | private var textField = { () -> CustomRoundedTextField in 60 | let textfield = CustomRoundedTextField() 61 | textfield.textColor = .white 62 | textfield.attributedPlaceholder = NSAttributedString(string: "Your idea...", attributes: [NSAttributedString.Key.foregroundColor: UIColor.regularLight]) 63 | textfield.translatesAutoresizingMaskIntoConstraints = false 64 | return textfield 65 | }() 66 | 67 | private var addButtonContainer = { () -> UIView in 68 | let container = UIView() 69 | container.translatesAutoresizingMaskIntoConstraints = false 70 | return container 71 | }() 72 | 73 | private var addButton = { () -> UIButton in 74 | let button = UIButton() 75 | button.setImage(UIImage(named: "send"), for: .normal) 76 | button.translatesAutoresizingMaskIntoConstraints = false 77 | button.addTarget(self, action: #selector(addButtonDidTap), for: .touchUpInside) 78 | return button 79 | }() 80 | 81 | private var closeButton = { () -> UIButton in 82 | let button = UIButton() 83 | button.setImage(UIImage(named: "close"), for: .normal) 84 | button.translatesAutoresizingMaskIntoConstraints = false 85 | button.addTarget(self, action: #selector(closeButtonDidTap), for: .touchUpInside) 86 | return button 87 | }() 88 | 89 | weak var delegate: AlertViewDelegate? 90 | 91 | // MARK: Initialize 92 | override init(frame: CGRect) { 93 | super.init(frame: frame) 94 | 95 | configureUI() 96 | } 97 | 98 | required init?(coder: NSCoder) { 99 | fatalError("init(coder:) has not been implemented") 100 | } 101 | 102 | func configureUI() { 103 | // adding shadow 104 | layer.shadowColor = UIColor.white.cgColor 105 | layer.shadowOpacity = 0.5 106 | layer.shadowOffset = .zero 107 | layer.shadowRadius = 4 108 | 109 | // adding subviews 110 | addSubview(backgroundView) 111 | addButtonContainer.addSubview(addButton) 112 | backgroundView.addSubview(containerView) 113 | 114 | containerView.addSubview(titleLabel) 115 | containerView.addSubview(errorLabel) 116 | containerView.addSubview(stackView) 117 | containerView.addSubview(closeButton) 118 | 119 | setupConstraints() 120 | } 121 | 122 | @objc func addButtonDidTap() { 123 | addButton.rotate() 124 | // validating map's name 125 | if let text = textField.text, !text.isEmpty, !text.trimmingCharacters(in: .whitespaces).isEmpty { 126 | if mapWithThisNameExist(name: text) { 127 | shakeTextField() 128 | errorLabel.text = "This map already exist" 129 | } else { 130 | textField.text = "" 131 | errorLabel.text = "" 132 | delegate?.addNode(name: text) 133 | } 134 | } else { 135 | shakeTextField() 136 | errorLabel.text = "Map name shouldn't be empty" 137 | } 138 | } 139 | 140 | func mapWithThisNameExist(name: String) -> Bool { 141 | do { 142 | let _ = try FileStorage().getFile(atPath: "\(name)\(String.mmdExtension)") 143 | return true 144 | } catch { 145 | return false 146 | } 147 | } 148 | 149 | 150 | @objc func closeButtonDidTap() { 151 | textField.text = "" 152 | delegate?.closeAlert() 153 | } 154 | 155 | private func shakeTextField() { 156 | let midX = textField.center.x 157 | let midY = textField.center.y 158 | let animation = CABasicAnimation(keyPath: "position") 159 | animation.duration = 0.06 160 | animation.repeatCount = 3 161 | animation.autoreverses = true 162 | animation.fromValue = CGPoint(x: midX - 6, y: midY) 163 | animation.toValue = CGPoint(x: midX + 6, y: midY) 164 | textField.layer.add(animation, forKey: "position") 165 | } 166 | 167 | // MARK: Constraints 168 | private func setupConstraints() { 169 | //background view 170 | backgroundView.topAnchor.constraint(equalTo: topAnchor).isActive = true 171 | backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 172 | backgroundView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true 173 | backgroundView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true 174 | 175 | //container View 176 | containerView.centerXAnchor.constraint(equalTo: backgroundView.centerXAnchor).isActive = true 177 | containerView.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor).isActive = true 178 | containerView.heightAnchor.constraint(equalToConstant: alertHeight).isActive = true 179 | 180 | // add button 181 | addButtonContainer.widthAnchor.constraint(equalToConstant: 30.0).isActive = true 182 | addButton.leftAnchor.constraint(equalTo: addButtonContainer.leftAnchor).isActive = true 183 | addButton.centerYAnchor.constraint(equalTo: addButtonContainer.centerYAnchor).isActive = true 184 | addButton.widthAnchor.constraint(equalToConstant: 30.0).isActive = true 185 | addButton.heightAnchor.constraint(equalToConstant: 30.0).isActive = true 186 | 187 | // title label 188 | titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16).isActive = true 189 | titleLabel.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 16).isActive = true 190 | titleLabel.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -16).isActive = true 191 | 192 | // error label 193 | errorLabel.topAnchor.constraint(equalTo: titleLabel.topAnchor, constant: 24).isActive = true 194 | errorLabel.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 16).isActive = true 195 | errorLabel.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -16).isActive = true 196 | 197 | // stackView 198 | stackView.topAnchor.constraint(equalTo: errorLabel.bottomAnchor, constant: 24).isActive = true 199 | stackView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 16).isActive = true 200 | stackView.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -16).isActive = true 201 | stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16).isActive = true 202 | stackView.widthAnchor.constraint(equalToConstant: 310).isActive = true 203 | 204 | // close button 205 | closeButton.widthAnchor.constraint(equalToConstant: 16.0).isActive = true 206 | closeButton.heightAnchor.constraint(equalToConstant: 16.0).isActive = true 207 | closeButton.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -16).isActive = true 208 | closeButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true 209 | } 210 | 211 | } 212 | 213 | -------------------------------------------------------------------------------- /MindMap/View/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /MindMap/View/CustomTextField.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class CustomRoundedTextField: UITextField { 4 | 5 | // MARK: Initialize 6 | override init(frame: CGRect) { 7 | super.init(frame: frame) 8 | sharedInit() 9 | } 10 | 11 | required init?(coder aDecoder: NSCoder) { 12 | super.init(coder: aDecoder) 13 | sharedInit() 14 | } 15 | 16 | // MARK: Functions 17 | private func sharedInit() { 18 | borderStyle = .none 19 | 20 | backgroundColor = UIColor.regularLight.withAlphaComponent(0.1) 21 | layer.borderColor = UIColor.regularLight.cgColor 22 | layer.borderWidth = 0.5 23 | layer.cornerRadius = 4 24 | } 25 | 26 | override var intrinsicContentSize: CGSize { 27 | return CGSize(width: UIView.noIntrinsicMetric, height: 50) 28 | } 29 | 30 | override func textRect(forBounds bounds: CGRect) -> CGRect { 31 | return super.textRect(forBounds: bounds).insetBy(dx: 8, dy: 0) 32 | } 33 | 34 | override func editingRect(forBounds bounds: CGRect) -> CGRect { 35 | return super.editingRect(forBounds: bounds).insetBy(dx: 8, dy: 0) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /MindMap/View/HomePageCollectionViewCell/FileCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | enum MapState: Codable { 4 | case locked 5 | case regular 6 | } 7 | 8 | protocol FileCollectionViewCellDelegate: AnyObject { 9 | func deleteMap(with name: String) 10 | func lockMap(state: MapState, with name: String) 11 | } 12 | 13 | class FileCollectionViewCell: UICollectionViewCell { 14 | 15 | var mapName: String? 16 | var state: MapState? 17 | weak var delegate: FileCollectionViewCellDelegate? 18 | 19 | @IBOutlet weak var containerView: UIView! 20 | @IBOutlet weak var previewImageView: UIImageView! 21 | @IBOutlet weak var nameButton: UIButton! 22 | @IBOutlet weak var editView: UIView! 23 | @IBOutlet weak var lockButton: UIButton! 24 | @IBOutlet weak var stateImageView: UIImageView! 25 | 26 | func configureUI(name: String, image: UIImage, state: MapState) { 27 | mapName = name 28 | self.state = state 29 | containerView.backgroundColor = UIColor.backgroundLight 30 | 31 | // adding shadow 32 | containerView.layer.shadowColor = UIColor.white.cgColor 33 | containerView.layer.shadowOpacity = 0.5 34 | containerView.layer.shadowOffset = .zero 35 | containerView.layer.shadowRadius = 4 36 | 37 | nameButton.setTitle(name, for: .normal) 38 | editView.isHidden = true 39 | 40 | stateImageView.isHidden = state == .regular ? true : false 41 | previewImageView.image = state == .regular ? image : UIImage(named: "folder") 42 | lockButton.setTitle(state == .regular ? "Lock map" : "Unlock map", for: .normal) 43 | } 44 | 45 | @IBAction func editDidTap(_ sender: Any) { 46 | UIView.transition(with: self.editView, duration: 0.4, options: .transitionCrossDissolve, animations: { 47 | self.editView.isHidden = !self.editView.isHidden 48 | }) 49 | } 50 | 51 | @IBAction func deleteDidTap(_ sender: Any) { 52 | if let mapName = mapName { 53 | delegate?.deleteMap(with: mapName) 54 | editView.isHidden = !editView.isHidden 55 | } 56 | } 57 | 58 | @IBAction func lockDidTap(_ sender: Any) { 59 | state = state == .regular ? .locked : .regular 60 | if let mapName = mapName, let state = state { 61 | delegate?.lockMap(state: state, with: mapName) 62 | editView.isHidden = !editView.isHidden 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /MindMap/View/HomePageCollectionViewCell/HeaderCollectionView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class HeaderCollectionView: UICollectionReusableView { 4 | 5 | @IBOutlet weak var titleLabel: UILabel! 6 | 7 | func configure(title: String) { 8 | titleLabel.text = title 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /MindMap/View/NodeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MegaView.swift 3 | // Mega Mindmap 4 | // 5 | // Created by Markus Karlsson on 2019-04-10. 6 | // Copyright © 2019 The App Factory AB. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum Direction { 12 | case top 13 | case bottom 14 | case left 15 | case right 16 | } 17 | 18 | protocol NodeViewDelegate { 19 | func addChildNode(parentNodeView: NodeView, at location: CGPoint) 20 | func didEditText(view: NodeView, text: String) 21 | func delete(view: NodeView) 22 | func expandView(direction: Direction) 23 | } 24 | 25 | class NodeView: UIView { 26 | // MARK: Stored properties 27 | var delegate: NodeViewDelegate? 28 | var splines = [SplineView]() 29 | var node: Node? 30 | var isRoot: Bool? 31 | 32 | let viewHeight: CGFloat = 120 33 | let minWidth: CGFloat = 120 34 | let maxWidth: CGFloat = 350 35 | let padding: CGFloat = 16 36 | 37 | lazy var textView: UITextView = { 38 | let label = UITextView() 39 | label.textAlignment = NSTextAlignment.center 40 | label.textColor = .lightGray 41 | label.text = "Type your idea here..." 42 | label.isEditable = true 43 | label.textColor = UIColor.white 44 | label.delegate = self 45 | label.backgroundColor = .clear 46 | label.font = .systemFont(ofSize: 16) 47 | label.autocorrectionType = .no 48 | return label 49 | }() 50 | 51 | // MARK: Initialize 52 | init(at position: CGPoint, name: String, node: Node, isRoot: Bool) { 53 | let size: CGFloat = 120 54 | let frame = CGRect(x: position.x - size / 2, y: position.y - size / 2, width: size, height: size) 55 | super.init(frame: frame) 56 | 57 | textView.frame = self.bounds 58 | textView.text = name 59 | addSubview(textView) 60 | 61 | self.node = node 62 | self.isRoot = isRoot 63 | 64 | // adding shadow 65 | layer.shadowColor = UIColor.white.cgColor 66 | layer.shadowOpacity = 0.5 67 | layer.shadowOffset = .zero 68 | layer.shadowRadius = 4 69 | 70 | backgroundColor = UIColor.clear 71 | 72 | // adding child 73 | addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))) 74 | 75 | // moving view 76 | let pan = UIPanGestureRecognizer(target: self, action: #selector(twoFingerPan(_:))) 77 | pan.minimumNumberOfTouches = 2 78 | pan.maximumNumberOfTouches = 2 79 | pan.delaysTouchesBegan = true 80 | addGestureRecognizer(pan) 81 | 82 | // contextMenu (editing + removing) 83 | let interaction = UIContextMenuInteraction(delegate: self) 84 | self.addInteraction(interaction) 85 | 86 | update() 87 | } 88 | 89 | required init?(coder aDecoder: NSCoder) { 90 | super.init(coder: aDecoder) 91 | } 92 | 93 | //MARK: - Functions 94 | func delete() { 95 | DispatchQueue.main.async { 96 | self.splines.forEach({ $0.removeFromSuperview() }) 97 | self.removeFromSuperview() 98 | } 99 | } 100 | 101 | func update() { 102 | let textSize = textView.sizeThatFits(CGSize(width: maxWidth - 2 * padding, height: viewHeight - 2 * padding)) 103 | let width = max(minWidth, textSize.width + 2 * padding) 104 | 105 | let labelSize = textView.getSize(constrainedWidth: width) 106 | 107 | textView.frame = CGRect(x: padding, y: padding, width: width - 2 * padding, height: labelSize.height ) 108 | frame = CGRect(x: center.x - width / 2, y: frame.origin.y, width: width, height: labelSize.height + 2 * padding) 109 | 110 | setNeedsDisplay() 111 | } 112 | 113 | override func draw(_ rect: CGRect) { 114 | let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.allCorners], cornerRadii: CGSize(width: 8, height: 8)) 115 | UIColor.backgroundViolet.setFill() 116 | path.fill() 117 | } 118 | 119 | @objc func didPan(gesture: UIPanGestureRecognizer) { 120 | switch gesture.state { 121 | case.changed: 122 | if let contentView = superview { 123 | needsExpand(location: gesture.location(in: contentView)) 124 | } 125 | case .ended: 126 | delegate?.addChildNode(parentNodeView: self, at: gesture.location(in: self.superview)) 127 | default: 128 | break 129 | } 130 | } 131 | 132 | @objc func twoFingerPan(_ gesture: UIPanGestureRecognizer) { 133 | // moving view & splines 134 | switch gesture.state { 135 | case .changed: 136 | self.center = gesture.location(in: self.superview) 137 | splines.forEach({ $0.update() }) 138 | if let contentView = superview { 139 | needsExpand(location: gesture.location(in: contentView)) 140 | } 141 | case .began: 142 | superview?.bringSubviewToFront(self) 143 | default: 144 | break 145 | } 146 | } 147 | 148 | func needsExpand(location: CGPoint) { 149 | if let contentView = superview { 150 | if contentView.frame.width - location.x <= 100 { 151 | delegate?.expandView(direction: .right) 152 | } 153 | 154 | if location.x <= 100 { 155 | delegate?.expandView(direction: .left) 156 | } 157 | 158 | if contentView.frame.height - location.y <= 100 { 159 | delegate?.expandView(direction: .bottom) 160 | } 161 | 162 | if location.y <= 100 { 163 | delegate?.expandView(direction: .top) 164 | } 165 | } 166 | } 167 | } 168 | 169 | //MARK: - UIContextMenuInteractionDelegate 170 | extension NodeView: UIContextMenuInteractionDelegate { 171 | 172 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { 173 | let removeIcon = UIImage(named: "delete") 174 | let editIcon = UIImage(named: "edit") 175 | 176 | return UIContextMenuConfiguration(identifier: "id" as NSCopying, previewProvider: nil, actionProvider: { _ in 177 | let editAction = UIAction(title: "Edit", image: editIcon, identifier: nil, discoverabilityTitle: nil) { _ in 178 | self.textView.becomeFirstResponder() 179 | } 180 | 181 | let removeAction = UIAction(title: "Remove", image: removeIcon, identifier: nil, discoverabilityTitle: nil, attributes: .destructive, handler: { _ in 182 | self.delegate?.delete(view: self) 183 | }) 184 | 185 | return UIMenu(title: "Edit", image: nil, options: [.displayInline], children: [editAction, removeAction]) 186 | }) 187 | } 188 | 189 | 190 | //MARK: - Stolen from https://kylebashour.com/posts/context-menu-guide 191 | // improved UI behaviour 192 | private func makeTargetedPreview(for configuration: UIContextMenuConfiguration) -> UITargetedPreview? { 193 | let visiblePath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 16) 194 | let parameters = UIPreviewParameters() 195 | parameters.visiblePath = visiblePath 196 | parameters.backgroundColor = .clear 197 | return UITargetedPreview(view: self, parameters: parameters) 198 | } 199 | 200 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { 201 | return self.makeTargetedPreview(for: configuration) 202 | } 203 | 204 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { 205 | return self.makeTargetedPreview(for: configuration) 206 | } 207 | } 208 | 209 | //MARK: - UITextViewDelegate 210 | extension NodeView: UITextViewDelegate { 211 | func textViewDidBeginEditing(_ textView: UITextView) { 212 | if textView.textColor != .white && textView.isFirstResponder { 213 | textView.text = nil 214 | textView.textColor = .white 215 | } 216 | } 217 | 218 | func textViewDidEndEditing(_ textView: UITextView) { 219 | if textView.text.isEmpty || textView.text == "" { 220 | textView.textColor = .lightGray 221 | textView.text = "Type your idea here..." 222 | } 223 | update() 224 | delegate?.didEditText(view: self, text: textView.text) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /MindMap/View/SplineView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SplineView: UIView { 4 | 5 | // MARK: Stored properties 6 | var parentView: NodeView? 7 | var childView: NodeView? 8 | 9 | var color: UIColor = .white 10 | 11 | // MARK: Initialize 12 | init(parentView: NodeView, childView: NodeView, color: UIColor) { 13 | super.init(frame: CGRect.zero) 14 | 15 | self.parentView = parentView 16 | self.childView = childView 17 | self.color = color 18 | 19 | backgroundColor = UIColor.clear 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | } 25 | 26 | // MARK: Functions 27 | func update() { 28 | if let parentView = parentView, let childView = childView { 29 | self.frame = parentView.frame.union(childView.frame) 30 | self.setNeedsDisplay() 31 | } 32 | } 33 | 34 | override func draw(_ rect: CGRect) { 35 | let path = UIBezierPath() 36 | path.lineWidth = 1.5 37 | 38 | self.color.setStroke() 39 | 40 | if let parentView = parentView, let childView = childView { 41 | let parentCenter = CGPoint(x: parentView.center.x - frame.origin.x, y: parentView.center.y - frame.origin.y) 42 | let childCenter = CGPoint(x: childView.center.x - frame.origin.x, y: childView.center.y - frame.origin.y) 43 | 44 | path.move(to: parentCenter) 45 | 46 | let point1 = CGPoint(x: parentCenter.x + (childCenter.x - parentCenter.x) / 2, y: parentCenter.y) 47 | let point2 = CGPoint(x: childCenter.x - (childCenter.x - parentCenter.x) / 2, y: childCenter.y) 48 | 49 | path.addCurve(to: childCenter, controlPoint1: point1, controlPoint2: point2) 50 | path.stroke() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'MindMap' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for MindMap 9 | pod 'IQKeyboardManagerSwift' 10 | 11 | end 12 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - IQKeyboardManagerSwift (6.5.6) 3 | 4 | DEPENDENCIES: 5 | - IQKeyboardManagerSwift 6 | 7 | SPEC REPOS: 8 | trunk: 9 | - IQKeyboardManagerSwift 10 | 11 | SPEC CHECKSUMS: 12 | IQKeyboardManagerSwift: c7df9d2deb356c04522f5c4b7b6e4ce4d8ed94fe 13 | 14 | PODFILE CHECKSUM: 9a6633194abfb806a44f4f824f06803bc7123faa 15 | 16 | COCOAPODS: 1.11.2 17 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQNSArray+Sort.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | /** 28 | UIView.subviews sorting category. 29 | */ 30 | internal extension Array where Element: UIView { 31 | 32 | ///-------------- 33 | /// MARK: Sorting 34 | ///-------------- 35 | 36 | /** 37 | Returns the array by sorting the UIView's by their tag property. 38 | */ 39 | func sortedArrayByTag() -> [Element] { 40 | 41 | return sorted(by: { (obj1: Element, obj2: Element) -> Bool in 42 | 43 | return (obj1.tag < obj2.tag) 44 | }) 45 | } 46 | 47 | /** 48 | Returns the array by sorting the UIView's by their tag property. 49 | */ 50 | func sortedArrayByPosition() -> [Element] { 51 | 52 | return sorted(by: { (obj1: Element, obj2: Element) -> Bool in 53 | if obj1.frame.minY != obj2.frame.minY { 54 | return obj1.frame.minY < obj2.frame.minY 55 | } else { 56 | return obj1.frame.minX < obj2.frame.minX 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIScrollView+Additions.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | private var kIQShouldIgnoreScrollingAdjustment = "kIQShouldIgnoreScrollingAdjustment" 28 | private var kIQShouldIgnoreContentInsetAdjustment = "kIQShouldIgnoreContentInsetAdjustment" 29 | private var kIQShouldRestoreScrollViewContentOffset = "kIQShouldRestoreScrollViewContentOffset" 30 | 31 | @objc public extension UIScrollView { 32 | 33 | /** 34 | If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textfield position. Default is NO. 35 | */ 36 | @objc var shouldIgnoreScrollingAdjustment: Bool { 37 | get { 38 | 39 | if let aValue = objc_getAssociatedObject(self, &kIQShouldIgnoreScrollingAdjustment) as? Bool { 40 | return aValue 41 | } else { 42 | return false 43 | } 44 | } 45 | set(newValue) { 46 | objc_setAssociatedObject(self, &kIQShouldIgnoreScrollingAdjustment, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 47 | } 48 | } 49 | 50 | /** 51 | If YES, then scrollview will ignore content inset adjustment (simply not updating it) when keyboard is shown. Default is NO. 52 | */ 53 | @objc var shouldIgnoreContentInsetAdjustment: Bool { 54 | get { 55 | 56 | if let aValue = objc_getAssociatedObject(self, &kIQShouldIgnoreContentInsetAdjustment) as? Bool { 57 | return aValue 58 | } else { 59 | return false 60 | } 61 | } 62 | set(newValue) { 63 | objc_setAssociatedObject(self, &kIQShouldIgnoreContentInsetAdjustment, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 64 | } 65 | } 66 | 67 | /** 68 | To set customized distance from keyboard for textField/textView. Can't be less than zero 69 | */ 70 | @objc var shouldRestoreScrollViewContentOffset: Bool { 71 | get { 72 | 73 | if let aValue = objc_getAssociatedObject(self, &kIQShouldRestoreScrollViewContentOffset) as? Bool { 74 | return aValue 75 | } else { 76 | return false 77 | } 78 | } 79 | set(newValue) { 80 | objc_setAssociatedObject(self, &kIQShouldRestoreScrollViewContentOffset, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 81 | } 82 | } 83 | } 84 | 85 | internal extension UITableView { 86 | 87 | func previousIndexPath(of indexPath: IndexPath) -> IndexPath? { 88 | var previousRow = indexPath.row - 1 89 | var previousSection = indexPath.section 90 | 91 | //Fixing indexPath 92 | if previousRow < 0 { 93 | previousSection -= 1 94 | 95 | if previousSection >= 0 { 96 | previousRow = self.numberOfRows(inSection: previousSection) - 1 97 | } 98 | } 99 | 100 | if previousRow >= 0 && previousSection >= 0 { 101 | return IndexPath(row: previousRow, section: previousSection) 102 | } else { 103 | return nil 104 | } 105 | } 106 | } 107 | 108 | internal extension UICollectionView { 109 | 110 | func previousIndexPath(of indexPath: IndexPath) -> IndexPath? { 111 | var previousRow = indexPath.row - 1 112 | var previousSection = indexPath.section 113 | 114 | //Fixing indexPath 115 | if previousRow < 0 { 116 | previousSection -= 1 117 | 118 | if previousSection >= 0 { 119 | previousRow = self.numberOfItems(inSection: previousSection) - 1 120 | } 121 | } 122 | 123 | if previousRow >= 0 && previousSection >= 0 { 124 | return IndexPath(item: previousRow, section: previousSection) 125 | } else { 126 | return nil 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITextFieldView+Additions.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | /** 28 | Uses default keyboard distance for textField. 29 | */ 30 | public let kIQUseDefaultKeyboardDistance = CGFloat.greatestFiniteMagnitude 31 | 32 | private var kIQKeyboardDistanceFromTextField = "kIQKeyboardDistanceFromTextField" 33 | private var kIQKeyboardEnableMode = "kIQKeyboardEnableMode" 34 | private var kIQShouldResignOnTouchOutsideMode = "kIQShouldResignOnTouchOutsideMode" 35 | private var kIQIgnoreSwitchingByNextPrevious = "kIQIgnoreSwitchingByNextPrevious" 36 | 37 | /** 38 | UIView category for managing UITextField/UITextView 39 | */ 40 | @objc public extension UIView { 41 | 42 | /** 43 | To set customized distance from keyboard for textField/textView. Can't be less than zero 44 | */ 45 | @objc var keyboardDistanceFromTextField: CGFloat { 46 | get { 47 | 48 | if let aValue = objc_getAssociatedObject(self, &kIQKeyboardDistanceFromTextField) as? CGFloat { 49 | return aValue 50 | } else { 51 | return kIQUseDefaultKeyboardDistance 52 | } 53 | } 54 | set(newValue) { 55 | objc_setAssociatedObject(self, &kIQKeyboardDistanceFromTextField, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 56 | } 57 | } 58 | 59 | /** 60 | If shouldIgnoreSwitchingByNextPrevious is true then library will ignore this textField/textView while moving to other textField/textView using keyboard toolbar next previous buttons. Default is false 61 | */ 62 | @objc var ignoreSwitchingByNextPrevious: Bool { 63 | get { 64 | 65 | if let aValue = objc_getAssociatedObject(self, &kIQIgnoreSwitchingByNextPrevious) as? Bool { 66 | return aValue 67 | } else { 68 | return false 69 | } 70 | } 71 | set(newValue) { 72 | objc_setAssociatedObject(self, &kIQIgnoreSwitchingByNextPrevious, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 73 | } 74 | } 75 | 76 | // /** 77 | // Override Enable/disable managing distance between keyboard and textField behaviour for this particular textField. 78 | // */ 79 | @objc var enableMode: IQEnableMode { 80 | get { 81 | 82 | if let savedMode = objc_getAssociatedObject(self, &kIQKeyboardEnableMode) as? IQEnableMode { 83 | return savedMode 84 | } else { 85 | return .default 86 | } 87 | } 88 | set(newValue) { 89 | objc_setAssociatedObject(self, &kIQKeyboardEnableMode, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 90 | } 91 | } 92 | 93 | /** 94 | Override resigns Keyboard on touching outside of UITextField/View behaviour for this particular textField. 95 | */ 96 | @objc var shouldResignOnTouchOutsideMode: IQEnableMode { 97 | get { 98 | 99 | if let savedMode = objc_getAssociatedObject(self, &kIQShouldResignOnTouchOutsideMode) as? IQEnableMode { 100 | return savedMode 101 | } else { 102 | return .default 103 | } 104 | } 105 | set(newValue) { 106 | objc_setAssociatedObject(self, &kIQShouldResignOnTouchOutsideMode, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIView+Hierarchy.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | /** 27 | UIView hierarchy category. 28 | */ 29 | @objc public extension UIView { 30 | 31 | ///---------------------- 32 | /// MARK: viewControllers 33 | ///---------------------- 34 | 35 | /** 36 | Returns the UIViewController object that manages the receiver. 37 | */ 38 | @objc func viewContainingController() -> UIViewController? { 39 | 40 | var nextResponder: UIResponder? = self 41 | 42 | repeat { 43 | nextResponder = nextResponder?.next 44 | 45 | if let viewController = nextResponder as? UIViewController { 46 | return viewController 47 | } 48 | 49 | } while nextResponder != nil 50 | 51 | return nil 52 | } 53 | 54 | /** 55 | Returns the topMost UIViewController object in hierarchy. 56 | */ 57 | @objc func topMostController() -> UIViewController? { 58 | 59 | var controllersHierarchy = [UIViewController]() 60 | 61 | if var topController = window?.rootViewController { 62 | controllersHierarchy.append(topController) 63 | 64 | while let presented = topController.presentedViewController { 65 | 66 | topController = presented 67 | 68 | controllersHierarchy.append(presented) 69 | } 70 | 71 | var matchController: UIResponder? = viewContainingController() 72 | 73 | while let mController = matchController as? UIViewController, controllersHierarchy.contains(mController) == false { 74 | 75 | repeat { 76 | matchController = matchController?.next 77 | 78 | } while matchController != nil && matchController is UIViewController == false 79 | } 80 | 81 | return matchController as? UIViewController 82 | 83 | } else { 84 | return viewContainingController() 85 | } 86 | } 87 | 88 | /** 89 | Returns the UIViewController object that is actually the parent of this object. Most of the time it's the viewController object which actually contains it, but result may be different if it's viewController is added as childViewController of another viewController. 90 | */ 91 | @objc func parentContainerViewController() -> UIViewController? { 92 | 93 | var matchController = viewContainingController() 94 | var parentContainerViewController: UIViewController? 95 | 96 | if var navController = matchController?.navigationController { 97 | 98 | while let parentNav = navController.navigationController { 99 | navController = parentNav 100 | } 101 | 102 | var parentController: UIViewController = navController 103 | 104 | while let parent = parentController.parent, 105 | (parent.isKind(of: UINavigationController.self) == false && 106 | parent.isKind(of: UITabBarController.self) == false && 107 | parent.isKind(of: UISplitViewController.self) == false) { 108 | 109 | parentController = parent 110 | } 111 | 112 | if navController == parentController { 113 | parentContainerViewController = navController.topViewController 114 | } else { 115 | parentContainerViewController = parentController 116 | } 117 | } else if let tabController = matchController?.tabBarController { 118 | 119 | if let navController = tabController.selectedViewController as? UINavigationController { 120 | parentContainerViewController = navController.topViewController 121 | } else { 122 | parentContainerViewController = tabController.selectedViewController 123 | } 124 | } else { 125 | while let parentController = matchController?.parent, 126 | (parentController.isKind(of: UINavigationController.self) == false && 127 | parentController.isKind(of: UITabBarController.self) == false && 128 | parentController.isKind(of: UISplitViewController.self) == false) { 129 | 130 | matchController = parentController 131 | } 132 | 133 | parentContainerViewController = matchController 134 | } 135 | 136 | let finalController = parentContainerViewController?.parentIQContainerViewController() ?? parentContainerViewController 137 | 138 | return finalController 139 | 140 | } 141 | 142 | ///----------------------------------- 143 | /// MARK: Superviews/Subviews/Siglings 144 | ///----------------------------------- 145 | 146 | /** 147 | Returns the superView of provided class type. 148 | 149 | 150 | @param classType class type of the object which is to be search in above hierarchy and return 151 | 152 | @param belowView view object in upper hierarchy where method should stop searching and return nil 153 | */ 154 | @objc func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? { 155 | 156 | var superView = superview 157 | 158 | while let unwrappedSuperView = superView { 159 | 160 | if unwrappedSuperView.isKind(of: classType) { 161 | 162 | //If it's UIScrollView, then validating for special cases 163 | if unwrappedSuperView.isKind(of: UIScrollView.self) { 164 | 165 | let classNameString = NSStringFromClass(type(of: unwrappedSuperView.self)) 166 | 167 | // If it's not UITableViewWrapperView class, this is internal class which is actually manage in UITableview. The speciality of this class is that it's superview is UITableView. 168 | // If it's not UITableViewCellScrollView class, this is internal class which is actually manage in UITableviewCell. The speciality of this class is that it's superview is UITableViewCell. 169 | //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes 170 | if unwrappedSuperView.superview?.isKind(of: UITableView.self) == false && 171 | unwrappedSuperView.superview?.isKind(of: UITableViewCell.self) == false && 172 | classNameString.hasPrefix("_") == false { 173 | return superView 174 | } 175 | } else { 176 | return superView 177 | } 178 | } else if unwrappedSuperView == belowView { 179 | return nil 180 | } 181 | 182 | superView = unwrappedSuperView.superview 183 | } 184 | 185 | return nil 186 | } 187 | 188 | /** 189 | Returns all siblings of the receiver which canBecomeFirstResponder. 190 | */ 191 | internal func responderSiblings() -> [UIView] { 192 | 193 | //Array of (UITextField/UITextView's). 194 | var tempTextFields = [UIView]() 195 | 196 | // Getting all siblings 197 | if let siblings = superview?.subviews { 198 | 199 | for textField in siblings { 200 | 201 | if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true { 202 | tempTextFields.append(textField) 203 | } 204 | } 205 | } 206 | 207 | return tempTextFields 208 | } 209 | 210 | /** 211 | Returns all deep subViews of the receiver which canBecomeFirstResponder. 212 | */ 213 | internal func deepResponderViews() -> [UIView] { 214 | 215 | //Array of (UITextField/UITextView's). 216 | var textfields = [UIView]() 217 | 218 | for textField in subviews { 219 | 220 | if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true { 221 | textfields.append(textField) 222 | } 223 | //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458) 224 | //Uncommented else (Bug ID: #625) 225 | else if textField.subviews.count != 0 && isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 { 226 | for deepView in textField.deepResponderViews() { 227 | textfields.append(deepView) 228 | } 229 | } 230 | } 231 | 232 | //subviews are returning in opposite order. Sorting according the frames 'y'. 233 | return textfields.sorted(by: { (view1: UIView, view2: UIView) -> Bool in 234 | 235 | let frame1 = view1.convert(view1.bounds, to: self) 236 | let frame2 = view2.convert(view2.bounds, to: self) 237 | 238 | if frame1.minY != frame2.minY { 239 | return frame1.minY < frame2.minY 240 | } else { 241 | return frame1.minX < frame2.minX 242 | } 243 | }) 244 | } 245 | 246 | private func IQcanBecomeFirstResponder() -> Bool { 247 | 248 | var IQcanBecomeFirstResponder = false 249 | 250 | if self.conforms(to: UITextInput.self) { 251 | // Setting toolbar to keyboard. 252 | if let textView = self as? UITextView { 253 | IQcanBecomeFirstResponder = textView.isEditable 254 | } else if let textField = self as? UITextField { 255 | IQcanBecomeFirstResponder = textField.isEnabled 256 | } 257 | } 258 | 259 | if IQcanBecomeFirstResponder == true { 260 | IQcanBecomeFirstResponder = isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 && isAlertViewTextField() == false && textFieldSearchBar() == nil 261 | } 262 | 263 | return IQcanBecomeFirstResponder 264 | } 265 | 266 | ///------------------------- 267 | /// MARK: Special TextFields 268 | ///------------------------- 269 | 270 | /** 271 | Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil. 272 | */ 273 | internal func textFieldSearchBar() -> UISearchBar? { 274 | 275 | var responder: UIResponder? = self.next 276 | 277 | while let bar = responder { 278 | 279 | if let searchBar = bar as? UISearchBar { 280 | return searchBar 281 | } else if bar is UIViewController { 282 | break 283 | } 284 | 285 | responder = bar.next 286 | } 287 | 288 | return nil 289 | } 290 | 291 | /** 292 | Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO. 293 | */ 294 | internal func isAlertViewTextField() -> Bool { 295 | 296 | var alertViewController: UIResponder? = viewContainingController() 297 | 298 | var isAlertViewTextField = false 299 | 300 | while let controller = alertViewController, isAlertViewTextField == false { 301 | 302 | if controller.isKind(of: UIAlertController.self) { 303 | isAlertViewTextField = true 304 | break 305 | } 306 | 307 | alertViewController = controller.next 308 | } 309 | 310 | return isAlertViewTextField 311 | } 312 | 313 | private func depth() -> Int { 314 | var depth: Int = 0 315 | 316 | if let superView = superview { 317 | depth = superView.depth()+1 318 | } 319 | 320 | return depth 321 | } 322 | 323 | } 324 | 325 | extension NSObject { 326 | 327 | internal func _IQDescription() -> String { 328 | return "<\(self) \(Unmanaged.passUnretained(self).toOpaque())>" 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIViewController+Additions.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | private var kIQLayoutGuideConstraint = "kIQLayoutGuideConstraint" 27 | 28 | @objc public extension UIViewController { 29 | 30 | /** 31 | This method is provided to override by viewController's if the library lifts a viewController which you doesn't want to lift . This may happen if you have implemented side menu feature in your app and the library try to lift the side menu controller. Overriding this method in side menu class to return correct controller should fix the problem. 32 | */ 33 | func parentIQContainerViewController() -> UIViewController? { 34 | return self 35 | } 36 | 37 | /** 38 | To set customized distance from keyboard for textField/textView. Can't be less than zero 39 | 40 | @deprecated Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview 41 | */ 42 | @available(*, deprecated, message: "Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview.") 43 | @IBOutlet @objc var IQLayoutGuideConstraint: NSLayoutConstraint? { 44 | get { 45 | 46 | return objc_getAssociatedObject(self, &kIQLayoutGuideConstraint) as? NSLayoutConstraint 47 | } 48 | 49 | set(newValue) { 50 | objc_setAssociatedObject(self, &kIQLayoutGuideConstraint, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQKeyboardManagerConstants.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | 26 | ///----------------------------------- 27 | /// MARK: IQAutoToolbarManageBehaviour 28 | ///----------------------------------- 29 | 30 | /** 31 | `IQAutoToolbarBySubviews` 32 | Creates Toolbar according to subview's hirarchy of Textfield's in view. 33 | 34 | `IQAutoToolbarByTag` 35 | Creates Toolbar according to tag property of TextField's. 36 | 37 | `IQAutoToolbarByPosition` 38 | Creates Toolbar according to the y,x position of textField in it's superview coordinate. 39 | */ 40 | @objc public enum IQAutoToolbarManageBehaviour: Int { 41 | case bySubviews 42 | case byTag 43 | case byPosition 44 | } 45 | 46 | /** 47 | `IQPreviousNextDisplayModeDefault` 48 | Show NextPrevious when there are more than 1 textField otherwise hide. 49 | 50 | `IQPreviousNextDisplayModeAlwaysHide` 51 | Do not show NextPrevious buttons in any case. 52 | 53 | `IQPreviousNextDisplayModeAlwaysShow` 54 | Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled. 55 | */ 56 | @objc public enum IQPreviousNextDisplayMode: Int { 57 | case `default` 58 | case alwaysHide 59 | case alwaysShow 60 | } 61 | 62 | /** 63 | `IQEnableModeDefault` 64 | Pick default settings. 65 | 66 | `IQEnableModeEnabled` 67 | setting is enabled. 68 | 69 | `IQEnableModeDisabled` 70 | setting is disabled. 71 | */ 72 | @objc public enum IQEnableMode: Int { 73 | case `default` 74 | case enabled 75 | case disabled 76 | } 77 | 78 | /* 79 | /---------------------------------------------------------------------------------------------------\ 80 | \---------------------------------------------------------------------------------------------------/ 81 | | iOS Notification Mechanism | 82 | /---------------------------------------------------------------------------------------------------\ 83 | \---------------------------------------------------------------------------------------------------/ 84 | 85 | ------------------------------------------------------------ 86 | When UITextField become first responder 87 | ------------------------------------------------------------ 88 | - UITextFieldTextDidBeginEditingNotification (UITextField) 89 | - UIKeyboardWillShowNotification 90 | - UIKeyboardDidShowNotification 91 | 92 | ------------------------------------------------------------ 93 | When UITextView become first responder 94 | ------------------------------------------------------------ 95 | - UIKeyboardWillShowNotification 96 | - UITextViewTextDidBeginEditingNotification (UITextView) 97 | - UIKeyboardDidShowNotification 98 | 99 | ------------------------------------------------------------ 100 | When switching focus from UITextField to another UITextField 101 | ------------------------------------------------------------ 102 | - UITextFieldTextDidEndEditingNotification (UITextField1) 103 | - UITextFieldTextDidBeginEditingNotification (UITextField2) 104 | - UIKeyboardWillShowNotification 105 | - UIKeyboardDidShowNotification 106 | 107 | ------------------------------------------------------------ 108 | When switching focus from UITextView to another UITextView 109 | ------------------------------------------------------------ 110 | - UITextViewTextDidEndEditingNotification: (UITextView1) 111 | - UIKeyboardWillShowNotification 112 | - UITextViewTextDidBeginEditingNotification: (UITextView2) 113 | - UIKeyboardDidShowNotification 114 | 115 | ------------------------------------------------------------ 116 | When switching focus from UITextField to UITextView 117 | ------------------------------------------------------------ 118 | - UITextFieldTextDidEndEditingNotification (UITextField) 119 | - UIKeyboardWillShowNotification 120 | - UITextViewTextDidBeginEditingNotification (UITextView) 121 | - UIKeyboardDidShowNotification 122 | 123 | ------------------------------------------------------------ 124 | When switching focus from UITextView to UITextField 125 | ------------------------------------------------------------ 126 | - UITextViewTextDidEndEditingNotification (UITextView) 127 | - UITextFieldTextDidBeginEditingNotification (UITextField) 128 | - UIKeyboardWillShowNotification 129 | - UIKeyboardDidShowNotification 130 | 131 | ------------------------------------------------------------ 132 | When opening/closing UIKeyboard Predictive bar 133 | ------------------------------------------------------------ 134 | - UIKeyboardWillShowNotification 135 | - UIKeyboardDidShowNotification 136 | 137 | ------------------------------------------------------------ 138 | On orientation change 139 | ------------------------------------------------------------ 140 | - UIApplicationWillChangeStatusBarOrientationNotification 141 | - UIKeyboardWillHideNotification 142 | - UIKeyboardDidHideNotification 143 | - UIApplicationDidChangeStatusBarOrientationNotification 144 | - UIKeyboardWillShowNotification 145 | - UIKeyboardDidShowNotification 146 | - UIKeyboardWillShowNotification 147 | - UIKeyboardDidShowNotification 148 | 149 | */ 150 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQKeyboardManagerConstantsInternal.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQTextView/IQTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQTextView.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | /** @abstract UITextView with placeholder support */ 27 | open class IQTextView: UITextView { 28 | 29 | @objc required public init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | 32 | #if swift(>=4.2) 33 | let UITextViewTextDidChange = UITextView.textDidChangeNotification 34 | #else 35 | let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange 36 | #endif 37 | 38 | NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self) 39 | } 40 | 41 | @objc override public init(frame: CGRect, textContainer: NSTextContainer?) { 42 | super.init(frame: frame, textContainer: textContainer) 43 | 44 | #if swift(>=4.2) 45 | let notificationName = UITextView.textDidChangeNotification 46 | #else 47 | let notificationName = Notification.Name.UITextViewTextDidChange 48 | #endif 49 | 50 | NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: notificationName, object: self) 51 | } 52 | 53 | @objc override open func awakeFromNib() { 54 | super.awakeFromNib() 55 | 56 | #if swift(>=4.2) 57 | let UITextViewTextDidChange = UITextView.textDidChangeNotification 58 | #else 59 | let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange 60 | #endif 61 | 62 | NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self) 63 | } 64 | 65 | deinit { 66 | IQ_PlaceholderLabel.removeFromSuperview() 67 | NotificationCenter.default.removeObserver(self) 68 | } 69 | 70 | private var placeholderInsets: UIEdgeInsets { 71 | return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding) 72 | } 73 | 74 | private var placeholderExpectedFrame: CGRect { 75 | let placeholderInsets = self.placeholderInsets 76 | let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right 77 | let expectedSize = IQ_PlaceholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom)) 78 | 79 | return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height) 80 | } 81 | 82 | lazy var IQ_PlaceholderLabel: UILabel = { 83 | let label = UILabel() 84 | 85 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 86 | label.lineBreakMode = .byWordWrapping 87 | label.numberOfLines = 0 88 | label.font = self.font 89 | label.textAlignment = self.textAlignment 90 | label.backgroundColor = UIColor.clear 91 | #if swift(>=5.1) 92 | label.textColor = UIColor.systemGray 93 | #else 94 | label.textColor = UIColor.lightText 95 | #endif 96 | label.alpha = 0 97 | self.addSubview(label) 98 | 99 | return label 100 | }() 101 | 102 | /** @abstract To set textView's placeholder text color. */ 103 | @IBInspectable open var placeholderTextColor: UIColor? { 104 | 105 | get { 106 | return IQ_PlaceholderLabel.textColor 107 | } 108 | 109 | set { 110 | IQ_PlaceholderLabel.textColor = newValue 111 | } 112 | } 113 | 114 | /** @abstract To set textView's placeholder text. Default is nil. */ 115 | @IBInspectable open var placeholder: String? { 116 | 117 | get { 118 | return IQ_PlaceholderLabel.text 119 | } 120 | 121 | set { 122 | IQ_PlaceholderLabel.text = newValue 123 | refreshPlaceholder() 124 | } 125 | } 126 | 127 | /** @abstract To set textView's placeholder attributed text. Default is nil. */ 128 | open var attributedPlaceholder: NSAttributedString? { 129 | get { 130 | return IQ_PlaceholderLabel.attributedText 131 | } 132 | 133 | set { 134 | IQ_PlaceholderLabel.attributedText = newValue 135 | refreshPlaceholder() 136 | } 137 | } 138 | 139 | @objc override open func layoutSubviews() { 140 | super.layoutSubviews() 141 | 142 | IQ_PlaceholderLabel.frame = placeholderExpectedFrame 143 | } 144 | 145 | @objc internal func refreshPlaceholder() { 146 | 147 | if !text.isEmpty || !attributedText.string.isEmpty { 148 | IQ_PlaceholderLabel.alpha = 0 149 | } else { 150 | IQ_PlaceholderLabel.alpha = 1 151 | } 152 | } 153 | 154 | @objc override open var text: String! { 155 | 156 | didSet { 157 | refreshPlaceholder() 158 | } 159 | } 160 | 161 | open override var attributedText: NSAttributedString! { 162 | 163 | didSet { 164 | refreshPlaceholder() 165 | } 166 | } 167 | 168 | @objc override open var font: UIFont? { 169 | 170 | didSet { 171 | 172 | if let unwrappedFont = font { 173 | IQ_PlaceholderLabel.font = unwrappedFont 174 | } else { 175 | IQ_PlaceholderLabel.font = UIFont.systemFont(ofSize: 12) 176 | } 177 | } 178 | } 179 | 180 | @objc override open var textAlignment: NSTextAlignment { 181 | didSet { 182 | IQ_PlaceholderLabel.textAlignment = textAlignment 183 | } 184 | } 185 | 186 | @objc override weak open var delegate: UITextViewDelegate? { 187 | 188 | get { 189 | refreshPlaceholder() 190 | return super.delegate 191 | } 192 | 193 | set { 194 | super.delegate = newValue 195 | } 196 | } 197 | 198 | @objc override open var intrinsicContentSize: CGSize { 199 | guard !hasText else { 200 | return super.intrinsicContentSize 201 | } 202 | 203 | var newSize = super.intrinsicContentSize 204 | let placeholderInsets = self.placeholderInsets 205 | newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom 206 | 207 | return newSize 208 | } 209 | } 210 | 211 | //#if swift(>=5.1) 212 | //import SwiftUI 213 | // 214 | //struct IQTextViewSwiftUI: UIViewRepresentable { 215 | // func makeUIView(context: Context) -> IQTextView { 216 | // IQTextView(frame: .zero) 217 | // } 218 | // 219 | // func updateUIView(_ view: IQTextView, context: Context) { 220 | // } 221 | //} 222 | // 223 | //struct IQTextViewSwiftUI_Preview: PreviewProvider { 224 | // static var previews: some View { 225 | // IQTextViewSwiftUI() 226 | // } 227 | //} 228 | // 229 | //#endif 230 | // 231 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQBarButtonItem.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | import Foundation 26 | 27 | open class IQBarButtonItem: UIBarButtonItem { 28 | 29 | private static var _classInitialize: Void = classInitialize() 30 | 31 | @objc public override init() { 32 | _ = IQBarButtonItem._classInitialize 33 | super.init() 34 | } 35 | 36 | @objc public required init?(coder aDecoder: NSCoder) { 37 | _ = IQBarButtonItem._classInitialize 38 | super.init(coder: aDecoder) 39 | } 40 | 41 | private class func classInitialize() { 42 | 43 | let appearanceProxy = self.appearance() 44 | 45 | #if swift(>=4.2) 46 | let states: [UIControl.State] 47 | #else 48 | let states: [UIControlState] 49 | #endif 50 | 51 | states = [.normal, .highlighted, .disabled, .selected, .application, .reserved] 52 | 53 | for state in states { 54 | 55 | appearanceProxy.setBackgroundImage(nil, for: state, barMetrics: .default) 56 | appearanceProxy.setBackgroundImage(nil, for: state, style: .done, barMetrics: .default) 57 | appearanceProxy.setBackgroundImage(nil, for: state, style: .plain, barMetrics: .default) 58 | appearanceProxy.setBackButtonBackgroundImage(nil, for: state, barMetrics: .default) 59 | } 60 | 61 | appearanceProxy.setTitlePositionAdjustment(UIOffset(), for: .default) 62 | appearanceProxy.setBackgroundVerticalPositionAdjustment(0, for: .default) 63 | appearanceProxy.setBackButtonBackgroundVerticalPositionAdjustment(0, for: .default) 64 | } 65 | 66 | @objc override open var tintColor: UIColor? { 67 | didSet { 68 | 69 | #if swift(>=4.2) 70 | var textAttributes = [NSAttributedString.Key: Any]() 71 | let foregroundColorKey = NSAttributedString.Key.foregroundColor 72 | #elseif swift(>=4) 73 | var textAttributes = [NSAttributedStringKey: Any]() 74 | let foregroundColorKey = NSAttributedStringKey.foregroundColor 75 | #else 76 | var textAttributes = [String: Any]() 77 | let foregroundColorKey = NSForegroundColorAttributeName 78 | #endif 79 | 80 | textAttributes[foregroundColorKey] = tintColor 81 | 82 | #if swift(>=4) 83 | 84 | if let attributes = titleTextAttributes(for: .normal) { 85 | 86 | for (key, value) in attributes { 87 | #if swift(>=4.2) 88 | textAttributes[key] = value 89 | #else 90 | textAttributes[NSAttributedStringKey.init(key)] = value 91 | #endif 92 | } 93 | } 94 | 95 | #else 96 | 97 | if let attributes = titleTextAttributes(for: .normal) { 98 | textAttributes = attributes 99 | } 100 | #endif 101 | 102 | setTitleTextAttributes(textAttributes, for: .normal) 103 | } 104 | } 105 | 106 | /** 107 | Boolean to know if it's a system item or custom item, we are having a limitation that we cannot override a designated initializer, so we are manually setting this property once in initialization 108 | */ 109 | @objc internal var isSystemItem = false 110 | 111 | /** 112 | Additional target & action to do get callback action. Note that setting custom target & selector doesn't affect native functionality, this is just an additional target to get a callback. 113 | 114 | @param target Target object. 115 | @param action Target Selector. 116 | */ 117 | @objc open func setTarget(_ target: AnyObject?, action: Selector?) { 118 | if let target = target, let action = action { 119 | invocation = IQInvocation(target, action) 120 | } else { 121 | invocation = nil 122 | } 123 | } 124 | 125 | /** 126 | Customized Invocation to be called when button is pressed. invocation is internally created using setTarget:action: method. 127 | */ 128 | @objc open var invocation: IQInvocation? 129 | 130 | deinit { 131 | target = nil 132 | invocation = nil 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQInvocation.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | @objc public class IQInvocation: NSObject { 27 | @objc public weak var target: AnyObject? 28 | @objc public var action: Selector 29 | 30 | @objc public init(_ target: AnyObject, _ action: Selector) { 31 | self.target = target 32 | self.action = action 33 | } 34 | 35 | @objc public func invoke(from: Any) { 36 | if let target = target { 37 | UIApplication.shared.sendAction(action, to: target, from: from, for: UIEvent()) 38 | } 39 | } 40 | 41 | deinit { 42 | target = nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQPreviousNextView.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | @objc public class IQPreviousNextView: UIView { 27 | 28 | } 29 | 30 | //#if swift(>=5.1) 31 | //import SwiftUI 32 | // 33 | //struct IQPreviousNextViewSwiftUI: UIViewRepresentable { 34 | // func makeUIView(context: Context) -> IQPreviousNextView { 35 | // IQPreviousNextView(frame: .zero) 36 | // } 37 | // 38 | // func updateUIView(_ view: IQPreviousNextView, context: Context) { 39 | // } 40 | //} 41 | // 42 | //struct IQTextViewSwiftUI_Preview: PreviewProvider { 43 | // static var previews: some View { 44 | // IQPreviousNextViewSwiftUI() 45 | // } 46 | //} 47 | // 48 | //#endif 49 | 50 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQTitleBarButtonItem.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | open class IQTitleBarButtonItem: IQBarButtonItem { 28 | 29 | @objc open var titleFont: UIFont? { 30 | 31 | didSet { 32 | if let unwrappedFont = titleFont { 33 | titleButton?.titleLabel?.font = unwrappedFont 34 | } else { 35 | titleButton?.titleLabel?.font = UIFont.systemFont(ofSize: 13) 36 | } 37 | } 38 | } 39 | 40 | @objc override open var title: String? { 41 | didSet { 42 | titleButton?.setTitle(title, for: .normal) 43 | } 44 | } 45 | 46 | /** 47 | titleColor to be used for displaying button text when displaying title (disabled state). 48 | */ 49 | @objc open var titleColor: UIColor? { 50 | 51 | didSet { 52 | 53 | if let color = titleColor { 54 | titleButton?.setTitleColor(color, for: .disabled) 55 | } else { 56 | titleButton?.setTitleColor(UIColor.lightGray, for: .disabled) 57 | } 58 | } 59 | } 60 | 61 | /** 62 | selectableTitleColor to be used for displaying button text when button is enabled. 63 | */ 64 | @objc open var selectableTitleColor: UIColor? { 65 | 66 | didSet { 67 | 68 | if let color = selectableTitleColor { 69 | titleButton?.setTitleColor(color, for: .normal) 70 | } else { 71 | #if swift(>=5.1) 72 | titleButton?.setTitleColor(UIColor.systemBlue, for: .normal) 73 | #else 74 | titleButton?.setTitleColor(UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) 75 | #endif 76 | } 77 | } 78 | } 79 | 80 | /** 81 | Customized Invocation to be called on title button action. titleInvocation is internally created using setTitleTarget:action: method. 82 | */ 83 | @objc override open var invocation: IQInvocation? { 84 | 85 | didSet { 86 | 87 | if let target = invocation?.target, let action = invocation?.action { 88 | self.isEnabled = true 89 | titleButton?.isEnabled = true 90 | titleButton?.addTarget(target, action: action, for: .touchUpInside) 91 | } else { 92 | self.isEnabled = false 93 | titleButton?.isEnabled = false 94 | titleButton?.removeTarget(nil, action: nil, for: .touchUpInside) 95 | } 96 | } 97 | } 98 | 99 | internal var titleButton: UIButton? 100 | private var _titleView: UIView? 101 | 102 | override init() { 103 | super.init() 104 | } 105 | 106 | @objc public convenience init(title: String?) { 107 | 108 | self.init(title: nil, style: .plain, target: nil, action: nil) 109 | 110 | _titleView = UIView() 111 | _titleView?.backgroundColor = UIColor.clear 112 | 113 | titleButton = UIButton(type: .system) 114 | titleButton?.isEnabled = false 115 | titleButton?.titleLabel?.numberOfLines = 3 116 | titleButton?.setTitleColor(UIColor.lightGray, for: .disabled) 117 | #if swift(>=5.1) 118 | titleButton?.setTitleColor(UIColor.systemBlue, for: .normal) 119 | #else 120 | titleButton?.setTitleColor(UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) 121 | #endif 122 | titleButton?.backgroundColor = UIColor.clear 123 | titleButton?.titleLabel?.textAlignment = .center 124 | titleButton?.setTitle(title, for: .normal) 125 | titleFont = UIFont.systemFont(ofSize: 13.0) 126 | titleButton?.titleLabel?.font = self.titleFont 127 | _titleView?.addSubview(titleButton!) 128 | 129 | if #available(iOS 11, *) { 130 | 131 | var layoutDefaultLowPriority: UILayoutPriority 132 | var layoutDefaultHighPriority: UILayoutPriority 133 | 134 | #if swift(>=4.0) 135 | let layoutPriorityLowValue = UILayoutPriority.defaultLow.rawValue-1 136 | let layoutPriorityHighValue = UILayoutPriority.defaultHigh.rawValue-1 137 | layoutDefaultLowPriority = UILayoutPriority(rawValue: layoutPriorityLowValue) 138 | layoutDefaultHighPriority = UILayoutPriority(rawValue: layoutPriorityHighValue) 139 | #else 140 | layoutDefaultLowPriority = UILayoutPriorityDefaultLow-1 141 | layoutDefaultHighPriority = UILayoutPriorityDefaultHigh-1 142 | #endif 143 | 144 | _titleView?.translatesAutoresizingMaskIntoConstraints = false 145 | _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical) 146 | _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal) 147 | _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical) 148 | _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal) 149 | 150 | titleButton?.translatesAutoresizingMaskIntoConstraints = false 151 | titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical) 152 | titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal) 153 | titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical) 154 | titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal) 155 | 156 | let top = NSLayoutConstraint.init(item: titleButton!, attribute: .top, relatedBy: .equal, toItem: _titleView, attribute: .top, multiplier: 1, constant: 0) 157 | let bottom = NSLayoutConstraint.init(item: titleButton!, attribute: .bottom, relatedBy: .equal, toItem: _titleView, attribute: .bottom, multiplier: 1, constant: 0) 158 | let leading = NSLayoutConstraint.init(item: titleButton!, attribute: .leading, relatedBy: .equal, toItem: _titleView, attribute: .leading, multiplier: 1, constant: 0) 159 | let trailing = NSLayoutConstraint.init(item: titleButton!, attribute: .trailing, relatedBy: .equal, toItem: _titleView, attribute: .trailing, multiplier: 1, constant: 0) 160 | 161 | _titleView?.addConstraints([top, bottom, leading, trailing]) 162 | } else { 163 | _titleView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] 164 | titleButton?.autoresizingMask = [.flexibleWidth, .flexibleHeight] 165 | } 166 | 167 | customView = _titleView 168 | } 169 | 170 | @objc required public init?(coder aDecoder: NSCoder) { 171 | super.init(coder: aDecoder) 172 | } 173 | 174 | deinit { 175 | customView = nil 176 | titleButton?.removeTarget(nil, action: nil, for: .touchUpInside) 177 | _titleView = nil 178 | titleButton = nil 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQToolbar.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | /** @abstract IQToolbar for IQKeyboardManager. */ 27 | open class IQToolbar: UIToolbar, UIInputViewAudioFeedback { 28 | 29 | private static var _classInitialize: Void = classInitialize() 30 | 31 | private class func classInitialize() { 32 | 33 | let appearanceProxy = self.appearance() 34 | 35 | appearanceProxy.barTintColor = nil 36 | 37 | let positions: [UIBarPosition] = [.any, .bottom, .top, .topAttached] 38 | 39 | for position in positions { 40 | 41 | appearanceProxy.setBackgroundImage(nil, forToolbarPosition: position, barMetrics: .default) 42 | appearanceProxy.setShadowImage(nil, forToolbarPosition: .any) 43 | } 44 | 45 | //Background color 46 | appearanceProxy.backgroundColor = nil 47 | } 48 | 49 | /** 50 | Previous bar button of toolbar. 51 | */ 52 | private var privatePreviousBarButton: IQBarButtonItem? 53 | @objc open var previousBarButton: IQBarButtonItem { 54 | get { 55 | if privatePreviousBarButton == nil { 56 | privatePreviousBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil) 57 | } 58 | return privatePreviousBarButton! 59 | } 60 | 61 | set (newValue) { 62 | privatePreviousBarButton = newValue 63 | } 64 | } 65 | 66 | /** 67 | Next bar button of toolbar. 68 | */ 69 | private var privateNextBarButton: IQBarButtonItem? 70 | @objc open var nextBarButton: IQBarButtonItem { 71 | get { 72 | if privateNextBarButton == nil { 73 | privateNextBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil) 74 | } 75 | return privateNextBarButton! 76 | } 77 | 78 | set (newValue) { 79 | privateNextBarButton = newValue 80 | } 81 | } 82 | 83 | /** 84 | Title bar button of toolbar. 85 | */ 86 | private var privateTitleBarButton: IQTitleBarButtonItem? 87 | @objc open var titleBarButton: IQTitleBarButtonItem { 88 | get { 89 | if privateTitleBarButton == nil { 90 | privateTitleBarButton = IQTitleBarButtonItem(title: nil) 91 | privateTitleBarButton?.accessibilityLabel = "Title" 92 | } 93 | return privateTitleBarButton! 94 | } 95 | 96 | set (newValue) { 97 | privateTitleBarButton = newValue 98 | } 99 | } 100 | 101 | /** 102 | Done bar button of toolbar. 103 | */ 104 | private var privateDoneBarButton: IQBarButtonItem? 105 | @objc open var doneBarButton: IQBarButtonItem { 106 | get { 107 | if privateDoneBarButton == nil { 108 | privateDoneBarButton = IQBarButtonItem(title: nil, style: .done, target: nil, action: nil) 109 | } 110 | return privateDoneBarButton! 111 | } 112 | 113 | set (newValue) { 114 | privateDoneBarButton = newValue 115 | } 116 | } 117 | 118 | /** 119 | Fixed space bar button of toolbar. 120 | */ 121 | private var privateFixedSpaceBarButton: IQBarButtonItem? 122 | @objc open var fixedSpaceBarButton: IQBarButtonItem { 123 | get { 124 | if privateFixedSpaceBarButton == nil { 125 | privateFixedSpaceBarButton = IQBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 126 | } 127 | privateFixedSpaceBarButton!.isSystemItem = true 128 | 129 | if #available(iOS 10, *) { 130 | privateFixedSpaceBarButton!.width = 6 131 | } else { 132 | privateFixedSpaceBarButton!.width = 20 133 | } 134 | 135 | return privateFixedSpaceBarButton! 136 | } 137 | 138 | set (newValue) { 139 | privateFixedSpaceBarButton = newValue 140 | } 141 | } 142 | 143 | override init(frame: CGRect) { 144 | _ = IQToolbar._classInitialize 145 | super.init(frame: frame) 146 | 147 | sizeToFit() 148 | 149 | autoresizingMask = .flexibleWidth 150 | self.isTranslucent = true 151 | } 152 | 153 | @objc required public init?(coder aDecoder: NSCoder) { 154 | _ = IQToolbar._classInitialize 155 | super.init(coder: aDecoder) 156 | 157 | sizeToFit() 158 | 159 | autoresizingMask = .flexibleWidth 160 | self.isTranslucent = true 161 | } 162 | 163 | @objc override open func sizeThatFits(_ size: CGSize) -> CGSize { 164 | var sizeThatFit = super.sizeThatFits(size) 165 | sizeThatFit.height = 44 166 | return sizeThatFit 167 | } 168 | 169 | @objc override open var tintColor: UIColor! { 170 | 171 | didSet { 172 | if let unwrappedItems = items { 173 | for item in unwrappedItems { 174 | item.tintColor = tintColor 175 | } 176 | } 177 | } 178 | } 179 | 180 | @objc override open func layoutSubviews() { 181 | 182 | super.layoutSubviews() 183 | 184 | if #available(iOS 11, *) { 185 | return 186 | } else if let customTitleView = titleBarButton.customView { 187 | var leftRect = CGRect.null 188 | var rightRect = CGRect.null 189 | var isTitleBarButtonFound = false 190 | 191 | let sortedSubviews = self.subviews.sorted(by: { (view1: UIView, view2: UIView) -> Bool in 192 | if view1.frame.minX != view2.frame.minX { 193 | return view1.frame.minX < view2.frame.minX 194 | } else { 195 | return view1.frame.minY < view2.frame.minY 196 | } 197 | }) 198 | 199 | for barButtonItemView in sortedSubviews { 200 | 201 | if isTitleBarButtonFound == true { 202 | rightRect = barButtonItemView.frame 203 | break 204 | } else if barButtonItemView === customTitleView { 205 | isTitleBarButtonFound = true 206 | //If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem) 207 | } else if barButtonItemView.isKind(of: UIControl.self) == true { 208 | leftRect = barButtonItemView.frame 209 | } 210 | } 211 | 212 | let titleMargin: CGFloat = 16 213 | 214 | let maxWidth: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) 215 | let maxHeight = self.frame.height 216 | 217 | let sizeThatFits = customTitleView.sizeThatFits(CGSize(width: maxWidth, height: maxHeight)) 218 | 219 | var titleRect: CGRect 220 | 221 | if sizeThatFits.width > 0 && sizeThatFits.height > 0 { 222 | let width = min(sizeThatFits.width, maxWidth) 223 | let height = min(sizeThatFits.height, maxHeight) 224 | 225 | var xPosition: CGFloat 226 | 227 | if leftRect.isNull == false { 228 | xPosition = titleMargin + leftRect.maxX + ((maxWidth - width)/2) 229 | } else { 230 | xPosition = titleMargin 231 | } 232 | 233 | let yPosition = (maxHeight - height)/2 234 | 235 | titleRect = CGRect(x: xPosition, y: yPosition, width: width, height: height) 236 | } else { 237 | 238 | var xPosition: CGFloat 239 | 240 | if leftRect.isNull == false { 241 | xPosition = titleMargin + leftRect.maxX 242 | } else { 243 | xPosition = titleMargin 244 | } 245 | 246 | let width: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) 247 | 248 | titleRect = CGRect(x: xPosition, y: 0, width: width, height: maxHeight) 249 | } 250 | 251 | customTitleView.frame = titleRect 252 | } 253 | } 254 | 255 | @objc open var enableInputClicksWhenVisible: Bool { 256 | return true 257 | } 258 | 259 | deinit { 260 | 261 | items = nil 262 | privatePreviousBarButton = nil 263 | privateNextBarButton = nil 264 | privateTitleBarButton = nil 265 | privateDoneBarButton = nil 266 | privateFixedSpaceBarButton = nil 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2017 Iftekhar Qurashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pods/IQKeyboardManagerSwift/README.md: -------------------------------------------------------------------------------- 1 |

2 | Icon 3 |

4 |

IQKeyboardManager

5 |

6 | GitHub license 8 | 9 | 10 | [![Build Status](https://travis-ci.org/hackiftekhar/IQKeyboardManager.svg)](https://travis-ci.org/hackiftekhar/IQKeyboardManager) 11 | 12 | 13 | While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the `UITextField/UITextView`. `IQKeyboardManager` allows you to prevent this issue of keyboard sliding up and covering `UITextField/UITextView` without needing you to write any code or make any additional setup. To use `IQKeyboardManager` you simply need to add source files to your project. 14 | 15 | 16 | #### Key Features 17 | 18 | 1) `**CODELESS**, Zero Lines of Code` 19 | 20 | 2) `Works Automatically` 21 | 22 | 3) `No More UIScrollView` 23 | 24 | 4) `No More Subclasses` 25 | 26 | 5) `No More Manual Work` 27 | 28 | 6) `No More #imports` 29 | 30 | `IQKeyboardManager` works on all orientations, and with the toolbar. It also has nice optional features allowing you to customize the distance from the text field, behaviour of previous, next and done buttons in the keyboard toolbar, play sound when the user navigates through the form and more. 31 | 32 | 33 | ## Screenshot 34 | [![IQKeyboardManager](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerScreenshot.png)](http://youtu.be/6nhLw6hju2A) 35 | [![Settings](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerSettings.png)](http://youtu.be/6nhLw6hju2A) 36 | 37 | ## GIF animation 38 | [![IQKeyboardManager](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManager.gif)](http://youtu.be/6nhLw6hju2A) 39 | 40 | ## Video 41 | 42 | IQKeyboardManager Demo Video 44 | 45 | ## Tutorial video by @rebeloper ([#1135](https://github.com/hackiftekhar/IQKeyboardManager/issues/1135)) 46 | 47 | @rebeloper demonstrated two videos on how to implement **IQKeyboardManager** at it's core: 48 | 49 | Youtube Tutorial Playlist 51 | 52 | https://www.youtube.com/playlist?list=PL_csAAO9PQ8aTL87XnueOXi3RpWE2m_8v 53 | 54 | ## Warning 55 | 56 | - **If you're planning to build SDK/library/framework and want to handle UITextField/UITextView with IQKeyboardManager then you're totally going the wrong way.** I would never suggest to add **IQKeyboardManager** as **dependency/adding/shipping** with any third-party library. Instead of adding **IQKeyboardManager** you should implement your own solution to achieve same kind of results. **IQKeyboardManager** is totally designed for projects to help developers for their convenience, it's not designed for **adding/dependency/shipping** with any **third-party library**, because **doing this could block adoption by other developers for their projects as well (who are not using IQKeyboardManager and have implemented their custom solution to handle UITextField/UITextView in the project).** 57 | - If **IQKeyboardManager** conflicts with other **third-party library**, then it's **developer responsibility** to **enable/disable IQKeyboardManager** when **presenting/dismissing** third-party library UI. Third-party libraries are not responsible to handle IQKeyboardManager. 58 | 59 | ## Requirements 60 | [![Platform iOS](https://img.shields.io/badge/Platform-iOS-blue.svg?style=fla)]() 61 | 62 | | | Language | Minimum iOS Target | Minimum Xcode Version | 63 | |------------------------|----------|--------------------|-----------------------| 64 | | IQKeyboardManager | Obj-C | iOS 8.0 | Xcode 9 | 65 | | IQKeyboardManagerSwift | Swift | iOS 8.0 | Xcode 9 | 66 | | Demo Project | | | Xcode 11 | 67 | 68 | #### Swift versions support 69 | 70 | | Swift | Xcode | IQKeyboardManagerSwift | 71 | |-------------------|-------|------------------------| 72 | | 5.1, 5.0, 4.2, 4.0, 3.2, 3.0| 11 | >= 6.5.0 | 73 | | 5.0,4.2, 4.0, 3.2, 3.0| 10.2 | >= 6.2.1 | 74 | | 4.2, 4.0, 3.2, 3.0| 10.0 | >= 6.0.4 | 75 | | 4.0, 3.2, 3.0 | 9.0 | 5.0.0 | 76 | 77 | Installation 78 | ========================== 79 | 80 | #### Installation with CocoaPods 81 | 82 | [![CocoaPods](https://img.shields.io/cocoapods/v/IQKeyboardManager.svg)](http://cocoadocs.org/docsets/IQKeyboardManager) 83 | 84 | ***IQKeyboardManager (Objective-C):*** IQKeyboardManager is available through [CocoaPods](http://cocoapods.org). To install 85 | it, simply add the following line to your Podfile: ([#9](https://github.com/hackiftekhar/IQKeyboardManager/issues/9)) 86 | 87 | ```ruby 88 | pod 'IQKeyboardManager' #iOS8 and later 89 | ``` 90 | 91 | ***IQKeyboardManager (Swift):*** IQKeyboardManagerSwift is available through [CocoaPods](http://cocoapods.org). To install 92 | it, simply add the following line to your Podfile: ([#236](https://github.com/hackiftekhar/IQKeyboardManager/issues/236)) 93 | 94 | *Swift 5.1, 5.0, 4.2, 4.0, 3.2, 3.0 (Xcode 11)* 95 | 96 | ```ruby 97 | pod 'IQKeyboardManagerSwift' 98 | ``` 99 | 100 | *Or you can choose the version you need based on Swift support table from [Requirements](README.md#requirements)* 101 | 102 | ```ruby 103 | pod 'IQKeyboardManagerSwift', '6.3.0' 104 | ``` 105 | 106 | In AppDelegate.swift, just import IQKeyboardManagerSwift framework and enable IQKeyboardManager. 107 | 108 | ```swift 109 | import IQKeyboardManagerSwift 110 | 111 | @UIApplicationMain 112 | class AppDelegate: UIResponder, UIApplicationDelegate { 113 | 114 | var window: UIWindow? 115 | 116 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 117 | 118 | IQKeyboardManager.shared.enable = true 119 | 120 | return true 121 | } 122 | } 123 | ``` 124 | 125 | #### Installation with Carthage 126 | 127 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 128 | 129 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 130 | 131 | ```bash 132 | $ brew update 133 | $ brew install carthage 134 | ``` 135 | 136 | To integrate `IQKeyboardManger` or `IQKeyboardManagerSwift` into your Xcode project using Carthage, add the following line to your `Cartfile`: 137 | 138 | ```ogdl 139 | github "hackiftekhar/IQKeyboardManager" 140 | ``` 141 | 142 | Run `carthage` to build the frameworks and drag the appropriate framework (`IQKeyboardManager.framework` or `IQKeyboardManagerSwift.framework`) into your Xcode project based on your need. Make sure to add only one framework and not both. 143 | 144 | 145 | #### Installation with Source Code 146 | 147 | [![Github tag](https://img.shields.io/github/tag/hackiftekhar/iqkeyboardmanager.svg)]() 148 | 149 | 150 | 151 | ***IQKeyboardManager (Objective-C):*** Just ***drag and drop*** `IQKeyboardManager` directory from demo project to your project. That's it. 152 | 153 | ***IQKeyboardManager (Swift):*** ***Drag and drop*** `IQKeyboardManagerSwift` directory from demo project to your project 154 | 155 | In AppDelegate.swift, just enable IQKeyboardManager. 156 | 157 | ```swift 158 | @UIApplicationMain 159 | class AppDelegate: UIResponder, UIApplicationDelegate { 160 | 161 | var window: UIWindow? 162 | 163 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 164 | 165 | IQKeyboardManager.shared.enable = true 166 | 167 | return true 168 | } 169 | } 170 | ``` 171 | 172 | #### Installation with Swift Package Manager 173 | 174 | [Swift Package Manager(SPM)](https://swift.org/package-manager/) is Apple's dependency manager tool. It is now supported in Xcode 11. So it can be used in all appleOS types of projects. It can be used alongside other tools like CocoaPods and Carthage as well. 175 | 176 | To install IQKeyboardManager package into your packages, add a reference to IQKeyboardManager and a targeting release version in the dependencies section in `Package.swift` file: 177 | 178 | ```swift 179 | import PackageDescription 180 | 181 | let package = Package( 182 | name: "YOUR_PROJECT_NAME", 183 | products: [], 184 | dependencies: [ 185 | .package(url: "https://github.com/hackiftekhar/IQKeyboardManager.git", from: "6.5.0") 186 | ] 187 | ) 188 | ``` 189 | 190 | To install IQKeyboardManager package via Xcode 191 | 192 | * Go to File -> Swift Packages -> Add Package Dependency... 193 | * Then search for https://github.com/hackiftekhar/IQKeyboardManager.git 194 | * And choose the version you want 195 | 196 | Migration Guide 197 | ========================== 198 | - [IQKeyboardManager 6.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/wiki/IQKeyboardManager-6.0.0-Migration-Guide) 199 | 200 | Other Links 201 | ========================== 202 | 203 | - [Known Issues](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Known-Issues) 204 | - [Manual Management Tweaks](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Manual-Management) 205 | - [Properties and functions usage](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Properties-&-Functions) 206 | 207 | ## Flow Diagram 208 | [![IQKeyboardManager CFD](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerFlowDiagram.jpg)](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerFlowDiagram.jpg) 209 | 210 | If you would like to see detailed Flow diagram then check [Detailed Flow Diagram](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerCFD.jpg). 211 | 212 | 213 | LICENSE 214 | --- 215 | Distributed under the MIT License. 216 | 217 | Contributions 218 | --- 219 | Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub. 220 | 221 | Author 222 | --- 223 | If you wish to contact me, email at: hack.iftekhar@gmail.com 224 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - IQKeyboardManagerSwift (6.5.6) 3 | 4 | DEPENDENCIES: 5 | - IQKeyboardManagerSwift 6 | 7 | SPEC REPOS: 8 | trunk: 9 | - IQKeyboardManagerSwift 10 | 11 | SPEC CHECKSUMS: 12 | IQKeyboardManagerSwift: c7df9d2deb356c04522f5c4b7b6e4ce4d8ed94fe 13 | 14 | PODFILE CHECKSUM: 9a6633194abfb806a44f4f824f06803bc7123faa 15 | 16 | COCOAPODS: 1.11.2 17 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/alina.xcuserdatad/xcschemes/IQKeyboardManagerSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/alina.xcuserdatad/xcschemes/Pods-MindMap.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/alina.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | IQKeyboardManagerSwift.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | Pods-MindMap.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 1 20 | 21 | 22 | SuppressBuildableAutocreation 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 6.5.6 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_IQKeyboardManagerSwift : NSObject 3 | @end 4 | @implementation PodsDummy_IQKeyboardManagerSwift 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double IQKeyboardManagerSwiftVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char IQKeyboardManagerSwiftVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "UIKit" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT} 10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/IQKeyboardManagerSwift 11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 13 | SKIP_INSTALL = YES 14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 15 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.modulemap: -------------------------------------------------------------------------------- 1 | framework module IQKeyboardManagerSwift { 2 | umbrella header "IQKeyboardManagerSwift-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "UIKit" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT} 10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/IQKeyboardManagerSwift 11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 13 | SKIP_INSTALL = YES 14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 15 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## IQKeyboardManagerSwift 5 | 6 | MIT License 7 | 8 | Copyright (c) 2013-2017 Iftekhar Qurashi 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT License 18 | 19 | Copyright (c) 2013-2017 Iftekhar Qurashi 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | IQKeyboardManagerSwift 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_MindMap : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_MindMap 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks-Debug-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks-Debug-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks-Release-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks-Release-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_MindMapVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_MindMapVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "IQKeyboardManagerSwift" -framework "QuartzCore" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_MindMap { 2 | umbrella header "Pods-MindMap-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-MindMap/Pods-MindMap.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "IQKeyboardManagerSwift" -framework "QuartzCore" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MindMap 2 | An iOS application for creating Mind Maps with clean/intuitive UI and MVC architecture. 3 | 4 | ## About the project 5 | - Check recently edited mind maps 6 | - Privacy (Lock mind maps) 7 | - Share with your friends 8 | 9 | 10 | | 11 | --- | --- 12 | 13 | 14 | ## Languages / Frameworks Used 15 | - Swift 5 16 | - UIKit 17 | - LocalAuthentication 18 | 19 | 20 | ## How to run the project ? 21 | * Fork the project. 22 | * Switch to the `main` branch. 23 | * Run the project using Xcode. 24 | --------------------------------------------------------------------------------