├── Screenshots ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png └── screenshot-4.png ├── TBRepeatPicker ├── Resources │ ├── Icons │ │ ├── TBRP-Checkmark@2x.png │ │ └── TBRP-Checkmark@3x.png │ └── Localizations │ │ ├── zh-Hant.lproj │ │ └── TBRPLocalizable.strings │ │ ├── zh-Hans.lproj │ │ └── TBRPLocalizable.strings │ │ ├── ja.lproj │ │ └── TBRPLocalizable.strings │ │ ├── ko.lproj │ │ └── TBRPLocalizable.strings │ │ └── en.lproj │ │ └── TBRPLocalizable.strings ├── Array+TBRPAddition.swift ├── String+TBRPAddition.swift ├── RangeReplaceableCollectionType+TBRPAddition.swift ├── UIColor+TBRPAddition.swift ├── NSCalendar+TBRPAddition.swift ├── TBRPCollectionViewLayout.swift ├── TBRPInternationalControl.swift ├── TBRPSwitchCell.swift ├── TBRepeatPicker.swift ├── TBRPCollectionItem.swift ├── TBRPCustomRepeatCell.swift ├── TBRPPickerViewCell.swift ├── TBRPCollectionViewCell.swift ├── TBRPPresetRepeatController.swift ├── TBRecurrence.swift ├── TBRPHelper.swift └── TBRPCustomRepeatController.swift ├── Example ├── TBRepeatPicker.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcuserdata │ │ └── hongxin.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── TBRepeatPicker.xcscheme │ └── project.pbxproj ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.swift ├── SwitchLanguageViewController.swift └── DemoViewController.swift ├── TBRepeatPicker.podspec ├── LICENSE.txt └── README.md /Screenshots/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongxinhope/TBRepeatPicker/HEAD/Screenshots/screenshot-1.png -------------------------------------------------------------------------------- /Screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongxinhope/TBRepeatPicker/HEAD/Screenshots/screenshot-2.png -------------------------------------------------------------------------------- /Screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongxinhope/TBRepeatPicker/HEAD/Screenshots/screenshot-3.png -------------------------------------------------------------------------------- /Screenshots/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongxinhope/TBRepeatPicker/HEAD/Screenshots/screenshot-4.png -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Icons/TBRP-Checkmark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongxinhope/TBRepeatPicker/HEAD/TBRepeatPicker/Resources/Icons/TBRP-Checkmark@2x.png -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Icons/TBRP-Checkmark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongxinhope/TBRepeatPicker/HEAD/TBRepeatPicker/Resources/Icons/TBRP-Checkmark@3x.png -------------------------------------------------------------------------------- /Example/TBRepeatPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TBRepeatPicker/Array+TBRPAddition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+TBRPAddition.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/10/8. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element : Hashable { 12 | var unique: [Element] { 13 | return Array(Set(self)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TBRepeatPicker/String+TBRPAddition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+TBRPAddition.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/10/8. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | mutating func removeSubstring(substring: String) { 13 | self = self.stringByReplacingOccurrencesOfString(substring, withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TBRepeatPicker/RangeReplaceableCollectionType+TBRPAddition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeReplaceableCollectionType+TBRPAddition.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/9/27. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension RangeReplaceableCollectionType where Generator.Element: Equatable { 12 | mutating func removeObject(object : Generator.Element) { 13 | if let index = self.indexOf(object) { 14 | self.removeAtIndex(index) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/TBRepeatPicker.xcodeproj/xcuserdata/hongxin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TBRepeatPicker.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | D3A7C1EA1BB26EAD000605DF 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /TBRepeatPicker/UIColor+TBRPAddition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+TBRPAddition.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/10/9. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | var rgbComponents:(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { 13 | var redValue:CGFloat = 0 14 | var greenValue:CGFloat = 0 15 | var blueValue:CGFloat = 0 16 | var alphaValue:CGFloat = 0 17 | 18 | getRed(&redValue, green: &greenValue, blue: &blueValue, alpha: &alphaValue) 19 | 20 | let bit: CGFloat = 255.0 21 | redValue *= bit 22 | greenValue *= bit 23 | blueValue *= bit 24 | 25 | return (redValue, greenValue, blueValue, alphaValue) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TBRepeatPicker.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = "TBRepeatPicker" 4 | s.version = "1.1.1" 5 | s.summary = "An event repeat rule picker similar to iOS system calendar." 6 | s.homepage = "https://github.com/hongxinhope/TBRepeatPicker" 7 | s.license = { :type => 'MIT', :file => 'LICENSE.txt' } 8 | s.author = { "Xin Hong" => "xin@teambition.com" } 9 | s.source = { :git => "https://github.com/hongxinhope/TBRepeatPicker.git", :tag => s.version.to_s } 10 | s.platform = :ios, '8.0' 11 | s.requires_arc = true 12 | s.source_files = "TBRepeatPicker/*.swift" 13 | s.resource_bundles = { 'TBRepeatPicker' => ['TBRepeatPicker/Resources/*/*.png', 'TBRepeatPicker/Resources/*/*.lproj'] } 14 | s.frameworks = "Foundation", "UIKit" 15 | 16 | end 17 | -------------------------------------------------------------------------------- /TBRepeatPicker/NSCalendar+TBRPAddition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCalendar+TBRPAddition.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/9/27. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSCalendar { 12 | class func dayIndexInWeek(date: NSDate) -> Int { 13 | return components(date).weekday 14 | } 15 | 16 | class func dayIndexInMonth(date: NSDate) -> Int { 17 | return components(date).day 18 | } 19 | 20 | class func monthIndexInYear(date: NSDate) -> Int { 21 | return components(date).month 22 | } 23 | 24 | private class func components(date: NSDate) -> NSDateComponents { 25 | let calendar = NSCalendar.currentCalendar() 26 | calendar.locale = NSLocale.currentLocale() 27 | let components = calendar.components([.Month, .Weekday, .Day], fromDate: date) 28 | 29 | return components 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPCollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPCollectionViewLayout.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/25. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TBRPCollectionViewLayout: UICollectionViewFlowLayout { 12 | private var mode: TBRPCollectionMode? 13 | 14 | convenience init(mode: TBRPCollectionMode) { 15 | self.init() 16 | 17 | self.mode = mode 18 | if mode == .Days { 19 | itemSize = CGSizeMake(TBRPDaysItemWidth, TBRPDaysItemHeight) 20 | } else { 21 | itemSize = CGSizeMake(TBRPMonthsItemWidth, TBRPMonthsItemHeight) 22 | } 23 | minimumInteritemSpacing = 0 24 | minimumLineSpacing = 0 25 | } 26 | 27 | override func collectionViewContentSize() -> CGSize { 28 | if mode == .Days { 29 | return CGSizeMake(TBRPScreenWidth, TBRPDaysCollectionHeight) 30 | } else { 31 | return CGSizeMake(TBRPScreenWidth, TBRPMonthsCollectionHeight) 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Teambition 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 | 23 | -------------------------------------------------------------------------------- /Example/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 | APPL 17 | CFBundleShortVersionString 18 | 1.1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 5 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPInternationalControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPInternationalControl.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/30. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class TBRPInternationalControl: NSObject { 12 | var language: TBRPLanguage = .English 13 | 14 | convenience init(language: TBRPLanguage!) { 15 | self.init() 16 | 17 | self.language = language 18 | } 19 | 20 | private func localizedForKey(key: String!) -> String? { 21 | let path = NSBundle.mainBundle().pathForResource(TBRPInternationalControl.languageKey(language), ofType: "lproj") 22 | if let _ = path { 23 | let bundle = NSBundle(path: path!) 24 | return bundle!.localizedStringForKey(key, value: nil, table: "TBRPLocalizable") 25 | } else { 26 | return nil 27 | } 28 | } 29 | 30 | func localized(key: String!, comment: String!) -> String { 31 | if let localizedString = localizedForKey(key) as String? { 32 | if localizedString == key { 33 | return comment 34 | } 35 | return localizedString 36 | } 37 | return comment 38 | } 39 | 40 | class func languageKey(language: TBRPLanguage) -> String { 41 | let languageKeys = ["en", "zh-Hans", "zh-Hant", "ko", "ja"] 42 | 43 | return languageKeys[language.rawValue] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPSwitchCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPSwitchCell.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/25. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let SwitchTrailingSpace: CGFloat = 15.0 12 | 13 | protocol TBRPSwitchCellDelegate { 14 | func didSwitch(sender: AnyObject) 15 | } 16 | 17 | class TBRPSwitchCell: TBRPCustomRepeatCell { 18 | var weekSwitch: UISwitch? 19 | var delegate: TBRPSwitchCellDelegate? 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | 24 | } 25 | 26 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 27 | super.init(style: style, reuseIdentifier: reuseIdentifier) 28 | 29 | weekSwitch = UISwitch() 30 | weekSwitch?.frame.origin.x = TBRPScreenWidth - (weekSwitch?.bounds.size.width)! - SwitchTrailingSpace 31 | weekSwitch?.frame.origin.y = contentView.bounds.size.height / 2 - (weekSwitch?.bounds.size.height)! / 2 32 | weekSwitch?.addTarget(self, action: "switchAction:", forControlEvents: .ValueChanged) 33 | contentView.addSubview(weekSwitch!) 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | func switchAction(sender: AnyObject) { 41 | if let _ = delegate { 42 | delegate?.didSwitch(sender) 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Example/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 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRepeatPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRepeatPicker.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/23. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /* 12 | enum TBRPLanguage: String { 13 | case English = "en" 14 | case SimplifiedChinese = "zh-Hans" 15 | case TraditionalChinese = "zh-Hant" 16 | case Korean = "ko" 17 | case Japanese = "ja" 18 | } 19 | */ 20 | 21 | /* 22 | To support Objective-C, we have to use integer enum. 23 | */ 24 | @objc enum TBRPLanguage: Int { 25 | case English = 0 26 | case SimplifiedChinese = 1 27 | case TraditionalChinese = 2 28 | case Korean = 3 29 | case Japanese = 4 30 | } 31 | 32 | class TBRepeatPicker: TBRPPresetRepeatController { 33 | 34 | /** Return an initialized repeat picker object. 35 | 36 | - Parameter occurrenceDate: The occurrence date of event. For repeat event, it's the occurrence date of this time. This property will be used for creating weekly/bi-weekly/monthly/yearly/custom recurrence, or judging the type of a recurrence. 37 | - Parameter language: Language of the picker, must be one of the following 5 supported languages: 38 | * TBRPLanguage.English 39 | * TBRPLanguage.SimplifiedChinese 40 | * TBRPLanguage.TraditionalChinese 41 | * TBRPLanguage.Korean 42 | * TBRPLanguage.Japanese 43 | - Parameter tintColor: A tint color which will be used in navigation bar, tableView, and the highlighted items. 44 | 45 | - Returns: An initialized repeat picker object. 46 | */ 47 | class func initPicker(occurrenceDate: NSDate, language: TBRPLanguage, tintColor: UIColor) -> TBRepeatPicker { 48 | let repeatPicker = TBRepeatPicker.init(style: .Grouped) 49 | 50 | repeatPicker.occurrenceDate = occurrenceDate 51 | repeatPicker.language = language 52 | repeatPicker.tintColor = tintColor 53 | 54 | return repeatPicker 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/23. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #TBRepeatPicker 2 | TBRepeatPicker is an event repeat rule picker similar to iOS system calendar. You can easily apply it in your project and you'll be happy to do it! 3 | 4 | ![Screenshot](Screenshots/screenshot-1.png "Screenshot") 5 | 6 | ![Screenshot](Screenshots/screenshot-2.png "Screenshot") 7 | 8 | ![Screenshot](Screenshots/screenshot-3.png "Screenshot") 9 | 10 | ##How To Get Started 11 | 12 | ### Podfile 13 | ```ruby 14 | platform :ios, '8.0' 15 | pod "TBRepeatPicker" 16 | ``` 17 | 18 | ###Usage 19 | ##### 1. Create and present TBRepeatPicker 20 | ```objective-c 21 | // give the occurrence date of event 22 | let dateFormatter = NSDateFormatter() 23 | dateFormatter.dateFormat = "yyyy-MM-dd" 24 | occurrenceDate = dateFormatter.dateFromString("2015-09-25")! 25 | 26 | // init picker 27 | let repeatPicker = TBRepeatPicker.initPicker(occurrenceDate, language: .English, tintColor: UIColor.blueColor()) 28 | 29 | // assign a recurrence to the picker, you can pass nil or do nothing here when the repeat rule is "Never". 30 | repeatPicker.recurrence = TBRecurrence.initMonthly(1, selectedMonthdays: [3, 17], occurrenceDate: occurrenceDate) 31 | 32 | // set delegate 33 | repeatPicker.delegate = self 34 | 35 | // push picker 36 | navigationController?.pushViewController(repeatPicker, animated: true) 37 | ``` 38 | Note: You should always use push segue here to present the picker, TBRepeatPicker doesn't support other segues such as modal. 39 | 40 | ##### 2. Implement didPickRecurrence delegate 41 | ```objective-c 42 | func didPickRecurrence(recurrence: TBRecurrence?, repeatPicker: TBRepeatPicker) { 43 | // do something 44 | } 45 | ``` 46 | 47 | ## Minimum Requirement 48 | iOS 8.0 49 | 50 | ## Localization 51 | TBRepeatPicker supports 5 languages: English, SimplifiedChinese, TraditionalChinese, Korean, Japanese. You can set the language when init. 52 | 53 | ## Swift and Objective-C 54 | TBRepeatPicker support both Swift and Objective-C. 55 | 56 | in Objective-C, you just need to import a header like this: 57 | ```objective-c 58 | #import "MyApp-Swift.h" 59 | ``` 60 | 61 | ## Release Notes 62 | * [Release Notes](https://github.com/hongxinhope/TBRepeatPicker/releases) 63 | 64 | ## License 65 | TBRepeatPicker is released under the MIT license. See [LICENSE](https://github.com/hongxinhope/TBRepeatPicker/blob/master/LICENSE.txt) for details. 66 | 67 | ## More Info 68 | Have a question? Please [open an issue](https://github.com/hongxinhope/TBRepeatPicker/issues/new)! 69 | 70 | -------------------------------------------------------------------------------- /Example/SwitchLanguageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchLanguageViewController.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/10/9. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let SwitchLanguageViewControllerCellID = "SwitchLanguageViewControllerCell" 12 | 13 | protocol SwitchLanguageViewControllerDelegate { 14 | func donePickingLanguage(language: TBRPLanguage) 15 | } 16 | 17 | class SwitchLanguageViewController: UITableViewController { 18 | var language: TBRPLanguage = .English 19 | var delegate: SwitchLanguageViewControllerDelegate? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .Plain, target: self, action: "cancelPressed") 25 | } 26 | 27 | func cancelPressed() { 28 | dismissViewControllerAnimated(true, completion: nil) 29 | } 30 | 31 | // MARK: - Table view data source 32 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 33 | return 1 34 | } 35 | 36 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 | return 5 38 | } 39 | 40 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 41 | var cell = tableView.dequeueReusableCellWithIdentifier(SwitchLanguageViewControllerCellID) 42 | if cell == nil { 43 | cell = UITableViewCell.init(style: .Default, reuseIdentifier: SwitchLanguageViewControllerCellID) 44 | } 45 | 46 | cell?.textLabel?.text = languageStrings[indexPath.row] 47 | 48 | if language == languages[indexPath.row] { 49 | cell?.accessoryType = .Checkmark 50 | } else { 51 | cell?.accessoryType = .None 52 | } 53 | 54 | return cell! 55 | } 56 | 57 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 58 | tableView.deselectRowAtIndexPath(indexPath, animated: true) 59 | 60 | language = languages[indexPath.row] 61 | tableView.reloadData() 62 | 63 | if let _ = delegate { 64 | dismissViewControllerAnimated(true, completion: { () -> Void in 65 | self.delegate?.donePickingLanguage(self.language) 66 | }) 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Localizations/zh-Hant.lproj/TBRPLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TBRepeatPicker 4 | 5 | Created by hongxin on 15/9/30. 6 | Copyright © 2015年 Teambition. All rights reserved. 7 | */ 8 | 9 | "TBRPHelper.frequencies.daily" = "每天"; 10 | "TBRPHelper.frequencies.weekly" = "每週"; 11 | "TBRPHelper.frequencies.monthly" = "每月"; 12 | "TBRPHelper.frequencies.yearly" = "每年"; 13 | 14 | "TBRPHelper.units.day" = "天"; 15 | "TBRPHelper.units.week" = "週"; 16 | "TBRPHelper.units.month" = "月"; 17 | "TBRPHelper.units.year" = "年"; 18 | 19 | "TBRPHelper.pluralUnits.days" = "天"; 20 | "TBRPHelper.pluralUnits.weeks" = "週"; 21 | "TBRPHelper.pluralUnits.months" = "個月"; 22 | "TBRPHelper.pluralUnits.years" = "年"; 23 | 24 | "TBRPHelper.presetRepeat.never" = "永不"; 25 | "TBRPHelper.presetRepeat.everyDay" = "每天"; 26 | "TBRPHelper.presetRepeat.everyWeek" = "每週"; 27 | "TBRPHelper.presetRepeat.everyTwoWeeks" = "每兩週"; 28 | "TBRPHelper.presetRepeat.everyMonth" = "每月"; 29 | "TBRPHelper.presetRepeat.everyYear" = "每年"; 30 | 31 | "TBRPHelper.numbersInWeekPicker.first" = "第一個"; 32 | "TBRPHelper.numbersInWeekPicker.second" = "第二個"; 33 | "TBRPHelper.numbersInWeekPicker.third" = "第三個"; 34 | "TBRPHelper.numbersInWeekPicker.fourth" = "第四個"; 35 | "TBRPHelper.numbersInWeekPicker.fifth" = "第五個"; 36 | "TBRPHelper.numbersInWeekPicker.last" = "最後一個"; 37 | 38 | "TBRPHelper.daysInWeekPicker.day" = "日子"; 39 | "TBRPHelper.daysInWeekPicker.weekday" = "平日"; 40 | "TBRPHelper.daysInWeekPicker.weekendDay" = "週末"; 41 | 42 | "TBRPPresetRepeatController.navigation.title" = "重複"; 43 | "TBRPPresetRepeatController.textLabel.custom" = "自定"; 44 | 45 | "TBRPCustomRepeatController.textLabel.frequency" = "頻率"; 46 | "TBRPCustomRepeatController.textLabel.interval" = "每"; 47 | "TBRPCustomRepeatController.textLabel.date" = "日期"; 48 | "TBRPCustomRepeatController.weekCell.onThe" = "星期"; 49 | "TBRPCustomRepeatController.weekCell.daysOfWeek" = "星期"; 50 | 51 | "RecurrenceString.presetRepeat" = "行程每%@重複一次。"; 52 | "RecurrenceString.element.on.weekly" = "的"; 53 | "RecurrenceString.element.on.monthly" = "的"; 54 | "RecurrenceString.element.on.yearlyMonths" = "的"; 55 | "RecurrenceString.element.on.yearlyMonths.byWeekNo" = "的"; 56 | "RecurrenceString.element.on.yearlyWeekString" = "的"; 57 | "RecurrenceString.element.and" = "和"; 58 | "RecurrenceString.element.comma" = "、"; 59 | "RecurrenceString.element.day" = "%d日"; 60 | "RecurrenceString.specifiedDaysOrMonths" = "行程每%@%@重複一次。"; 61 | "RecurrenceString.yearlyByWeekNoString" = "行程每%@%@%@重複一次。"; 62 | "RecurrenceString.weekdayRecurrence" = "行程每週一到週五重複一次。"; 63 | -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Localizations/zh-Hans.lproj/TBRPLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TBRepeatPicker 4 | 5 | Created by hongxin on 15/9/30. 6 | Copyright © 2015年 Teambition. All rights reserved. 7 | */ 8 | 9 | "TBRPHelper.frequencies.daily" = "每天"; 10 | "TBRPHelper.frequencies.weekly" = "每周"; 11 | "TBRPHelper.frequencies.monthly" = "每月"; 12 | "TBRPHelper.frequencies.yearly" = "每年"; 13 | 14 | "TBRPHelper.units.day" = "天"; 15 | "TBRPHelper.units.week" = "周"; 16 | "TBRPHelper.units.month" = "月"; 17 | "TBRPHelper.units.year" = "年"; 18 | 19 | "TBRPHelper.pluralUnits.days" = "天"; 20 | "TBRPHelper.pluralUnits.weeks" = "周"; 21 | "TBRPHelper.pluralUnits.months" = "个月"; 22 | "TBRPHelper.pluralUnits.years" = "年"; 23 | 24 | "TBRPHelper.presetRepeat.never" = "永不"; 25 | "TBRPHelper.presetRepeat.everyDay" = "每天"; 26 | "TBRPHelper.presetRepeat.everyWeek" = "每周"; 27 | "TBRPHelper.presetRepeat.everyTwoWeeks" = "每两周"; 28 | "TBRPHelper.presetRepeat.everyMonth" = "每月"; 29 | "TBRPHelper.presetRepeat.everyYear" = "每年"; 30 | 31 | "TBRPHelper.numbersInWeekPicker.first" = "第一个"; 32 | "TBRPHelper.numbersInWeekPicker.second" = "第二个"; 33 | "TBRPHelper.numbersInWeekPicker.third" = "第三个"; 34 | "TBRPHelper.numbersInWeekPicker.fourth" = "第四个"; 35 | "TBRPHelper.numbersInWeekPicker.fifth" = "第五个"; 36 | "TBRPHelper.numbersInWeekPicker.last" = "最后一个"; 37 | 38 | "TBRPHelper.daysInWeekPicker.day" = "自然日"; 39 | "TBRPHelper.daysInWeekPicker.weekday" = "工作日"; 40 | "TBRPHelper.daysInWeekPicker.weekendDay" = "周末"; 41 | 42 | "TBRPPresetRepeatController.navigation.title" = "重复"; 43 | "TBRPPresetRepeatController.textLabel.custom" = "自定义"; 44 | 45 | "TBRPCustomRepeatController.textLabel.frequency" = "频率"; 46 | "TBRPCustomRepeatController.textLabel.interval" = "每"; 47 | "TBRPCustomRepeatController.textLabel.date" = "日期"; 48 | "TBRPCustomRepeatController.weekCell.onThe" = "星期"; 49 | "TBRPCustomRepeatController.weekCell.daysOfWeek" = "星期"; 50 | 51 | "RecurrenceString.presetRepeat" = "事件将每%@重复一次。"; 52 | "RecurrenceString.element.on.weekly" = "于"; 53 | "RecurrenceString.element.on.monthly" = "于"; 54 | "RecurrenceString.element.on.yearlyMonths" = "于"; 55 | "RecurrenceString.element.on.yearlyMonths.byWeekNo" = "于"; 56 | "RecurrenceString.element.on.yearlyWeekString" = "的"; 57 | "RecurrenceString.element.and" = "和"; 58 | "RecurrenceString.element.comma" = "、"; 59 | "RecurrenceString.element.day" = "%d日"; 60 | "RecurrenceString.specifiedDaysOrMonths" = "事件将每%@%@重复一次。"; 61 | "RecurrenceString.yearlyByWeekNoString" = "事件将每%@%@%@重复一次。"; 62 | "RecurrenceString.weekdayRecurrence" = "事件将每个工作日重复一次。"; 63 | -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Localizations/ja.lproj/TBRPLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TBRepeatPicker 4 | 5 | Created by hongxin on 15/9/30. 6 | Copyright © 2015年 Teambition. All rights reserved. 7 | */ 8 | 9 | "TBRPHelper.frequencies.daily" = "毎日"; 10 | "TBRPHelper.frequencies.weekly" = "毎週"; 11 | "TBRPHelper.frequencies.monthly" = "毎月"; 12 | "TBRPHelper.frequencies.yearly" = "毎年"; 13 | 14 | "TBRPHelper.units.day" = "日"; 15 | "TBRPHelper.units.week" = "週間"; 16 | "TBRPHelper.units.month" = "カ月"; 17 | "TBRPHelper.units.year" = "年"; 18 | 19 | "TBRPHelper.pluralUnits.days" = "日"; 20 | "TBRPHelper.pluralUnits.weeks" = "週間"; 21 | "TBRPHelper.pluralUnits.months" = "カ月"; 22 | "TBRPHelper.pluralUnits.years" = "年"; 23 | 24 | "TBRPHelper.presetRepeat.never" = "しない"; 25 | "TBRPHelper.presetRepeat.everyDay" = "毎日"; 26 | "TBRPHelper.presetRepeat.everyWeek" = "毎週"; 27 | "TBRPHelper.presetRepeat.everyTwoWeeks" = "2週間ごと"; 28 | "TBRPHelper.presetRepeat.everyMonth" = "毎月"; 29 | "TBRPHelper.presetRepeat.everyYear" = "毎年"; 30 | 31 | "TBRPHelper.numbersInWeekPicker.first" = "第1"; 32 | "TBRPHelper.numbersInWeekPicker.second" = "第2"; 33 | "TBRPHelper.numbersInWeekPicker.third" = "第3"; 34 | "TBRPHelper.numbersInWeekPicker.fourth" = "第4"; 35 | "TBRPHelper.numbersInWeekPicker.fifth" = "第5"; 36 | "TBRPHelper.numbersInWeekPicker.last" = "最後の"; 37 | 38 | "TBRPHelper.daysInWeekPicker.day" = "日"; 39 | "TBRPHelper.daysInWeekPicker.weekday" = "平日"; 40 | "TBRPHelper.daysInWeekPicker.weekendDay" = "週末"; 41 | 42 | "TBRPPresetRepeatController.navigation.title" = "繰り返し"; 43 | "TBRPPresetRepeatController.textLabel.custom" = "カスタム"; 44 | 45 | "TBRPCustomRepeatController.textLabel.frequency" = "繰り返しの単位"; 46 | "TBRPCustomRepeatController.textLabel.interval" = "繰り返しの頻度"; 47 | "TBRPCustomRepeatController.textLabel.date" = "毎"; 48 | "TBRPCustomRepeatController.weekCell.onThe" = "指定日"; 49 | "TBRPCustomRepeatController.weekCell.daysOfWeek" = "曜日"; 50 | 51 | "RecurrenceString.presetRepeat" = "%@ごとにあるイベントです。"; 52 | "RecurrenceString.element.on.weekly" = "に、"; 53 | "RecurrenceString.element.on.monthly" = "に、"; 54 | "RecurrenceString.element.on.yearlyMonths" = "に、"; 55 | "RecurrenceString.element.on.yearlyMonths.byWeekNo" = "に、"; 56 | "RecurrenceString.element.on.yearlyWeekString" = "の"; 57 | "RecurrenceString.element.and" = "と"; 58 | "RecurrenceString.element.comma" = "、"; 59 | "RecurrenceString.element.day" = "%d日"; 60 | "RecurrenceString.specifiedDaysOrMonths" = "%@ごと%@にあるイベントです。"; 61 | "RecurrenceString.yearlyByWeekNoString" = "%@ごと%@%@にあるイベントです。"; 62 | "RecurrenceString.weekdayRecurrence" = "平日に毎日あるイベントです。"; 63 | -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Localizations/ko.lproj/TBRPLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TBRepeatPicker 4 | 5 | Created by hongxin on 15/9/30. 6 | Copyright © 2015年 Teambition. All rights reserved. 7 | */ 8 | 9 | "TBRPHelper.frequencies.daily" = "매일"; 10 | "TBRPHelper.frequencies.weekly" = "매주"; 11 | "TBRPHelper.frequencies.monthly" = "매월"; 12 | "TBRPHelper.frequencies.yearly" = "매년"; 13 | 14 | "TBRPHelper.units.day" = "일"; 15 | "TBRPHelper.units.week" = "주"; 16 | "TBRPHelper.units.month" = "개월"; 17 | "TBRPHelper.units.year" = "년"; 18 | 19 | "TBRPHelper.pluralUnits.days" = "일"; 20 | "TBRPHelper.pluralUnits.weeks" = "주"; 21 | "TBRPHelper.pluralUnits.months" = "개월"; 22 | "TBRPHelper.pluralUnits.years" = "년"; 23 | 24 | "TBRPHelper.presetRepeat.never" = "안 함"; 25 | "TBRPHelper.presetRepeat.everyDay" = "매일"; 26 | "TBRPHelper.presetRepeat.everyWeek" = "매주"; 27 | "TBRPHelper.presetRepeat.everyTwoWeeks" = "2주마다"; 28 | "TBRPHelper.presetRepeat.everyMonth" = "매월"; 29 | "TBRPHelper.presetRepeat.everyYear" = "매년"; 30 | 31 | "TBRPHelper.numbersInWeekPicker.first" = "첫 번째"; 32 | "TBRPHelper.numbersInWeekPicker.second" = "두 번째"; 33 | "TBRPHelper.numbersInWeekPicker.third" = "세 번째"; 34 | "TBRPHelper.numbersInWeekPicker.fourth" = "네 번째"; 35 | "TBRPHelper.numbersInWeekPicker.fifth" = "다섯 번째"; 36 | "TBRPHelper.numbersInWeekPicker.last" = "최근"; 37 | 38 | "TBRPHelper.daysInWeekPicker.day" = "요일"; 39 | "TBRPHelper.daysInWeekPicker.weekday" = "평일"; 40 | "TBRPHelper.daysInWeekPicker.weekendDay" = "주말"; 41 | 42 | "TBRPPresetRepeatController.navigation.title" = "반복"; 43 | "TBRPPresetRepeatController.textLabel.custom" = "사용자 설정"; 44 | 45 | "TBRPCustomRepeatController.textLabel.frequency" = "반복 주기"; 46 | "TBRPCustomRepeatController.textLabel.interval" = "반복"; 47 | "TBRPCustomRepeatController.textLabel.date" = "일자 지정"; 48 | "TBRPCustomRepeatController.weekCell.onThe" = "조건 지정..."; 49 | "TBRPCustomRepeatController.weekCell.daysOfWeek" = "요일"; 50 | 51 | "RecurrenceString.presetRepeat" = "%@마다 이벤트 반복"; 52 | "RecurrenceString.element.on.weekly" = "에"; 53 | "RecurrenceString.element.on.monthly" = "에"; 54 | "RecurrenceString.element.on.yearlyMonths" = "에"; 55 | "RecurrenceString.element.on.yearlyMonths.byWeekNo" = "에"; 56 | "RecurrenceString.element.on.yearlyWeekString" = "의"; 57 | "RecurrenceString.element.and" = "및"; 58 | "RecurrenceString.element.comma" = ","; 59 | "RecurrenceString.element.day" = "%d일"; 60 | "RecurrenceString.specifiedDaysOrMonths" = "%@마다 %@ 이벤트 반복"; 61 | "RecurrenceString.yearlyByWeekNoString" = "%@마다 %@ %@ 이벤트 반복"; 62 | "RecurrenceString.weekdayRecurrence" = "매주 평일마다 이벤트 반복"; 63 | -------------------------------------------------------------------------------- /TBRepeatPicker/Resources/Localizations/en.lproj/TBRPLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TBRepeatPicker 4 | 5 | Created by hongxin on 15/9/30. 6 | Copyright © 2015年 Teambition. All rights reserved. 7 | */ 8 | 9 | "TBRPHelper.frequencies.daily" = "Daily"; 10 | "TBRPHelper.frequencies.weekly" = "Weekly"; 11 | "TBRPHelper.frequencies.monthly" = "Monthly"; 12 | "TBRPHelper.frequencies.yearly" = "Yearly"; 13 | 14 | "TBRPHelper.units.day" = "Day"; 15 | "TBRPHelper.units.week" = "Week"; 16 | "TBRPHelper.units.month" = "Month"; 17 | "TBRPHelper.units.year" = "Year"; 18 | 19 | "TBRPHelper.pluralUnits.days" = "days"; 20 | "TBRPHelper.pluralUnits.weeks" = "weeks"; 21 | "TBRPHelper.pluralUnits.months" = "months"; 22 | "TBRPHelper.pluralUnits.years" = "years"; 23 | 24 | "TBRPHelper.presetRepeat.never" = "Never"; 25 | "TBRPHelper.presetRepeat.everyDay" = "Every Day"; 26 | "TBRPHelper.presetRepeat.everyWeek" = "Every Week"; 27 | "TBRPHelper.presetRepeat.everyTwoWeeks" = "Every 2 Weeks"; 28 | "TBRPHelper.presetRepeat.everyMonth" = "Every Month"; 29 | "TBRPHelper.presetRepeat.everyYear" = "Every Year"; 30 | 31 | "TBRPHelper.numbersInWeekPicker.first" = "first"; 32 | "TBRPHelper.numbersInWeekPicker.second" = "second"; 33 | "TBRPHelper.numbersInWeekPicker.third" = "third"; 34 | "TBRPHelper.numbersInWeekPicker.fourth" = "fourth"; 35 | "TBRPHelper.numbersInWeekPicker.fifth" = "fifth"; 36 | "TBRPHelper.numbersInWeekPicker.last" = "last"; 37 | 38 | "TBRPHelper.daysInWeekPicker.day" = "day"; 39 | "TBRPHelper.daysInWeekPicker.weekday" = "weekday"; 40 | "TBRPHelper.daysInWeekPicker.weekendDay" = "weekend day"; 41 | 42 | "TBRPPresetRepeatController.navigation.title" = "Repeat"; 43 | "TBRPPresetRepeatController.textLabel.custom" = "Custom"; 44 | 45 | "TBRPCustomRepeatController.textLabel.frequency" = "Frequency"; 46 | "TBRPCustomRepeatController.textLabel.interval" = "Every"; 47 | "TBRPCustomRepeatController.textLabel.date" = "Each"; 48 | "TBRPCustomRepeatController.weekCell.onThe" = "On the..."; 49 | "TBRPCustomRepeatController.weekCell.daysOfWeek" = "Days of Week"; 50 | 51 | "RecurrenceString.presetRepeat" = "Event will occur every %@."; 52 | "RecurrenceString.element.on.weekly" = "on"; 53 | "RecurrenceString.element.on.monthly" = "on the"; 54 | "RecurrenceString.element.on.yearlyMonths" = "in"; 55 | "RecurrenceString.element.on.yearlyMonths.byWeekNo" = "of"; 56 | "RecurrenceString.element.on.yearlyWeekString" = "on the"; 57 | "RecurrenceString.element.and" = "and"; 58 | "RecurrenceString.element.comma" = ","; 59 | "RecurrenceString.element.day" = ""; 60 | "RecurrenceString.specifiedDaysOrMonths" = "Event will occur every %@ %@."; 61 | "RecurrenceString.yearlyByWeekNoString" = "Event will occur every %@ %@ %@."; 62 | "RecurrenceString.weekdayRecurrence" = "Event will occur every weekday."; 63 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPCollectionItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPCollectionItem.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/25. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let lineWidth: CGFloat = 0.5 12 | 13 | class TBRPCollectionItem: UICollectionViewCell { 14 | // MARK: - Public properties 15 | var textLabel: UILabel? 16 | var showTopLine = true 17 | var showBottomLine = true 18 | var showLeftLine = true 19 | var showRightLine = true 20 | 21 | // MARK: - Private properties 22 | private let topLine = CALayer() 23 | private let bottomLine = CALayer() 24 | private let leftLine = CALayer() 25 | private let rightLine = CALayer() 26 | 27 | // MARK: - View life cycle 28 | override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | 31 | textLabel = UILabel(frame: CGRectMake(lineWidth, lineWidth, bounds.size.width - 2 * lineWidth, bounds.size.height - 2 * lineWidth)) 32 | textLabel?.textAlignment = .Center 33 | backgroundView = textLabel 34 | 35 | // add separator line 36 | topLine.frame = CGRectMake(0, 0, bounds.size.width, lineWidth) 37 | topLine.backgroundColor = separatorColor() 38 | 39 | bottomLine.frame = CGRectMake(0, bounds.size.height - lineWidth, bounds.size.width, lineWidth) 40 | bottomLine.backgroundColor = separatorColor() 41 | 42 | leftLine.frame = CGRectMake(0, 0, lineWidth, bounds.size.height) 43 | leftLine.backgroundColor = separatorColor() 44 | 45 | rightLine.frame = CGRectMake(bounds.size.width - lineWidth, 0, lineWidth, bounds.size.height) 46 | rightLine.backgroundColor = separatorColor() 47 | 48 | layer.addSublayer(topLine) 49 | layer.addSublayer(bottomLine) 50 | layer.addSublayer(leftLine) 51 | layer.addSublayer(rightLine) 52 | } 53 | 54 | required init?(coder aDecoder: NSCoder) { 55 | fatalError("init(coder:) has not been implemented") 56 | } 57 | 58 | override func layoutSubviews() { 59 | super.layoutSubviews() 60 | 61 | topLine.hidden = !showTopLine 62 | bottomLine.hidden = !showBottomLine 63 | leftLine.hidden = !showLeftLine 64 | rightLine.hidden = !showRightLine 65 | } 66 | 67 | // MARK: - Helper 68 | private func separatorColor() -> CGColorRef { 69 | return UIColor.init(white: 187.0 / 255.0, alpha: 1.0).CGColor 70 | } 71 | 72 | func setItemSelected(selected: Bool) { 73 | if selected == true { 74 | textLabel?.backgroundColor = tintColor 75 | textLabel?.textColor = UIColor.whiteColor() 76 | } else { 77 | textLabel?.backgroundColor = UIColor.whiteColor() 78 | textLabel?.textColor = UIColor.blackColor() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPCustomRepeatCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPCustomRepeatCell.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/29. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TBRPCustomRepeatCell: UITableViewCell { 12 | // MARK: - View life cycle 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | 16 | } 17 | 18 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 19 | super.init(style: style, reuseIdentifier: reuseIdentifier) 20 | 21 | addDefaultBottomSeparator() 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override func layoutSubviews() { 29 | super.layoutSubviews() 30 | 31 | textLabel?.frame.origin.x = TBRPHelper.leadingMargin() 32 | } 33 | 34 | override func prepareForReuse() { 35 | super.prepareForReuse() 36 | 37 | resetSeparatorWithLeftX(TBRPHelper.leadingMargin()) 38 | } 39 | 40 | // MARK: - Separator line 41 | func removeAllSeparators() { 42 | for sublayer in layer.sublayers! { 43 | if sublayer.name == TBRPTopSeparatorIdentifier || sublayer.name == TBRPBottomSeparatorIdentifier { 44 | sublayer.removeFromSuperlayer() 45 | } 46 | } 47 | } 48 | 49 | func removeBottomSeparators() { 50 | for sublayer in layer.sublayers! { 51 | if sublayer.name == TBRPBottomSeparatorIdentifier { 52 | sublayer.removeFromSuperlayer() 53 | } 54 | } 55 | } 56 | 57 | func addBottomSeparatorFromLeftX(leftX: CGFloat) { 58 | for sublayer in layer.sublayers! { 59 | if sublayer.name == TBRPBottomSeparatorIdentifier { 60 | sublayer.removeFromSuperlayer() 61 | } 62 | } 63 | 64 | let bottomSeparator = CALayer() 65 | bottomSeparator.name = TBRPBottomSeparatorIdentifier 66 | 67 | bottomSeparator.frame = CGRectMake(leftX, bounds.size.height - TBRPSeparatorLineWidth, TBRPScreenWidth - leftX, TBRPSeparatorLineWidth) 68 | bottomSeparator.backgroundColor = TBRPHelper.separatorColor() 69 | 70 | layer.addSublayer(bottomSeparator) 71 | } 72 | 73 | func addTopSeparatorFromLeftX(leftX: CGFloat) { 74 | for sublayer in layer.sublayers! { 75 | if sublayer.name == TBRPTopSeparatorIdentifier { 76 | sublayer.removeFromSuperlayer() 77 | } 78 | } 79 | 80 | let topSeparator = CALayer() 81 | topSeparator.name = TBRPTopSeparatorIdentifier 82 | 83 | topSeparator.frame = CGRectMake(leftX, 0,TBRPScreenWidth - leftX, TBRPSeparatorLineWidth) 84 | topSeparator.backgroundColor = TBRPHelper.separatorColor() 85 | 86 | layer.addSublayer(topSeparator) 87 | } 88 | 89 | func addDefaultBottomSeparator() { 90 | addBottomSeparatorFromLeftX(TBRPHelper.leadingMargin()) 91 | } 92 | 93 | func addSectionTopSeparator() { 94 | addTopSeparatorFromLeftX(0) 95 | } 96 | 97 | func addSectionBottomSeparator() { 98 | addBottomSeparatorFromLeftX(0) 99 | } 100 | 101 | func updateBottomSeparatorWithLeftX(leftX: CGFloat) { 102 | removeBottomSeparators() 103 | addBottomSeparatorFromLeftX(leftX) 104 | } 105 | 106 | func resetSeparatorWithLeftX(leftX: CGFloat) { 107 | removeAllSeparators() 108 | addBottomSeparatorFromLeftX(leftX) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Example/TBRepeatPicker.xcodeproj/xcuserdata/hongxin.xcuserdatad/xcschemes/TBRepeatPicker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Example/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/30. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let languageStrings = ["English", "SimplifiedChinese", "TraditionalChinese", "Korean", "Japanese"] 12 | let languages: [TBRPLanguage] = [.English, .SimplifiedChinese, .TraditionalChinese, .Korean, .Japanese] 13 | 14 | class DemoViewController: UIViewController, TBRepeatPickerDelegate, SwitchLanguageViewControllerDelegate { 15 | var occurrenceDate = NSDate() 16 | var recurrence: TBRecurrence? 17 | var language: TBRPLanguage = .English 18 | 19 | @IBOutlet weak var resultTextView: UITextView! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | // let dateFormatter = NSDateFormatter() 25 | // dateFormatter.dateFormat = "yyyy-MM-dd" 26 | // occurrenceDate = dateFormatter.dateFromString("2015-09-14")! // 2015-09-14 Monday 27 | 28 | resultTextView.userInteractionEnabled = false 29 | updateLanguageTitle() 30 | updateResultTextView() 31 | } 32 | 33 | private func updateLanguageTitle() { 34 | let languageIndex = languages.indexOf(language) 35 | let languageTitle = languageStrings[languageIndex!] 36 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: languageTitle, style: .Plain, target: self, action: "switchLanguage") 37 | } 38 | 39 | func switchLanguage() { 40 | let switchLanguageViewController = SwitchLanguageViewController.init(style: .Grouped) 41 | let navigationViewController = UINavigationController(rootViewController: switchLanguageViewController) 42 | switchLanguageViewController.language = language 43 | switchLanguageViewController.delegate = self 44 | 45 | presentViewController(navigationViewController, animated: true, completion: nil) 46 | } 47 | 48 | func donePickingLanguage(language: TBRPLanguage) { 49 | self.language = language 50 | updateLanguageTitle() 51 | updateResultTextView() 52 | } 53 | 54 | @IBAction func startPicking(sender: UIButton) { 55 | let repeatPicker = TBRepeatPicker.initPicker(occurrenceDate, language: language, tintColor: tbBlueColor()) 56 | repeatPicker.delegate = self 57 | 58 | // recurrence = nil 59 | // 60 | // recurrence = TBRecurrence.initDaily(1, occurrenceDate: occurrenceDate) 61 | // 62 | // recurrence = TBRecurrence.initWeekly(1, selectedWeekdays: [], occurrenceDate: occurrenceDate) 63 | // recurrence = TBRecurrence.initWeekly(1, selectedWeekdays: [2, 2, 6], occurrenceDate: occurrenceDate) 64 | // recurrence = TBRecurrence.initWeekly(2, selectedWeekdays: [], occurrenceDate: occurrenceDate) 65 | // recurrence = TBRecurrence.initWeekly(2, selectedWeekdays: [1, 3, 5, 6, 0], occurrenceDate: occurrenceDate) 66 | // 67 | // recurrence = TBRecurrence.initMonthly(1, selectedMonthdays: [], occurrenceDate: occurrenceDate) 68 | // recurrence = TBRecurrence.initMonthly(2, selectedMonthdays: [], occurrenceDate: occurrenceDate) 69 | // recurrence = TBRecurrence.initMonthly(1, selectedMonthdays: [3, 17], occurrenceDate: occurrenceDate) 70 | // recurrence = TBRecurrence.initMonthly(3, selectedMonthdays: [5, 20], occurrenceDate: occurrenceDate) 71 | // 72 | // recurrence = TBRecurrence.initMonthlyByWeekNumber(1, pickedWeekNumber: .Second, pickedWeekday: .Day, occurrenceDate: occurrenceDate) 73 | // recurrence = TBRecurrence.initMonthlyByWeekNumber(4, pickedWeekNumber: .Third, pickedWeekday: .Weekday, occurrenceDate: occurrenceDate) 74 | // 75 | // recurrence = TBRecurrence.initYearly(1, selectedMonths: [], occurrenceDate: occurrenceDate) 76 | // recurrence = TBRecurrence.initYearly(4, selectedMonths: [], occurrenceDate: occurrenceDate) 77 | // recurrence = TBRecurrence.initYearly(1, selectedMonths: [3, 8], occurrenceDate: occurrenceDate) 78 | // recurrence = TBRecurrence.initYearly(3, selectedMonths: [7, 9], occurrenceDate: occurrenceDate) 79 | // 80 | // recurrence = TBRecurrence.initYearlyByWeekNumber(1, pickedWeekNumber: .Fifth, pickedWeekday: .Wednesday, occurrenceDate: occurrenceDate) 81 | // recurrence = TBRecurrence.initYearlyByWeekNumber(3, pickedWeekNumber: .Last, pickedWeekday: .Day, occurrenceDate: occurrenceDate) 82 | // 83 | // recurrence = TBRecurrence.dailyRecurrence(occurrenceDate) 84 | // recurrence = TBRecurrence.weeklyRecurrence(occurrenceDate) 85 | // recurrence = TBRecurrence.biWeeklyRecurrence(occurrenceDate) 86 | // recurrence = TBRecurrence.monthlyRecurrence(occurrenceDate) 87 | // recurrence = TBRecurrence.yearlyRecurrence(occurrenceDate) 88 | // recurrence = TBRecurrence.weekdayRecurrence(occurrenceDate) 89 | 90 | repeatPicker.recurrence = recurrence 91 | 92 | navigationController?.pushViewController(repeatPicker, animated: true) 93 | } 94 | 95 | func didPickRecurrence(recurrence: TBRecurrence?, repeatPicker: TBRepeatPicker) { 96 | self.recurrence = recurrence 97 | 98 | updateResultTextView() 99 | } 100 | 101 | private func updateResultTextView() { 102 | if let _ = recurrence { 103 | resultTextView.text = TBRPHelper.recurrenceString(recurrence!, occurrenceDate: occurrenceDate, language: language) 104 | } else { 105 | resultTextView.text = "Never Repeat" 106 | } 107 | } 108 | 109 | func tbBlueColor() -> UIColor { 110 | return UIColor.init(red: 3.0 / 255.0, green: 169.0 / 255.0, blue: 244.0 / 255.0, alpha: 1.0) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPPickerViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPPickerViewCell.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/24. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum TBRPPickerStyle { 12 | case Frequency 13 | case Interval 14 | case Week 15 | } 16 | 17 | let TBRPPickerHeight: CGFloat = 215.0 18 | 19 | private let TBRPPickerRowHeight: CGFloat = 40.0 20 | private let TBRPPickerMaxRowCount = 999 21 | 22 | protocol TBRPPickerCellDelegate { 23 | func pickerDidPick(pickerView: UIPickerView, pickStyle: TBRPPickerStyle, didSelectRow row: Int, inComponent component: Int) 24 | } 25 | 26 | class TBRPPickerViewCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate { 27 | // MARK: - Public properties 28 | var language: TBRPLanguage = .English 29 | var pickerStyle: TBRPPickerStyle? 30 | var delegate: TBRPPickerCellDelegate? 31 | var unit: String? { 32 | didSet { 33 | pickerView?.reloadComponent(1) 34 | } 35 | } 36 | var frequency: TBRPFrequency? { 37 | didSet { 38 | pickerView?.selectRow((frequency?.rawValue)!, inComponent: 0, animated: true) 39 | } 40 | } 41 | var interval: Int? { 42 | didSet { 43 | pickerView?.selectRow(interval! - 1, inComponent: 0, animated: true) 44 | } 45 | } 46 | var pickedWeekNumber: TBRPWeekPickerNumber? { 47 | didSet { 48 | pickerView?.selectRow((pickedWeekNumber?.rawValue)!, inComponent: 0, animated: true) 49 | } 50 | } 51 | var pickedWeekday: TBRPWeekPickerDay? { 52 | didSet { 53 | pickerView?.selectRow((pickedWeekday?.rawValue)!, inComponent: 1, animated: true) 54 | } 55 | } 56 | 57 | // MARK: - Private properties 58 | private var pickerView: UIPickerView? 59 | 60 | // MARK: - Initialization 61 | override func awakeFromNib() { 62 | super.awakeFromNib() 63 | 64 | } 65 | 66 | override func prepareForReuse() { 67 | super.prepareForReuse() 68 | 69 | removeAllSeparators() 70 | } 71 | 72 | convenience init(style: UITableViewCellStyle, reuseIdentifier: String?, pickerStyle: TBRPPickerStyle, language: TBRPLanguage) { 73 | self.init() 74 | 75 | pickerView = UIPickerView.init(frame: CGRectMake(0, 0, TBRPScreenWidth, TBRPPickerHeight)) 76 | pickerView!.dataSource = self 77 | pickerView!.delegate = self 78 | 79 | self.pickerStyle = pickerStyle 80 | if pickerStyle == .Frequency { 81 | addDefaultBottomSeparator() 82 | } else { 83 | addSectionBottomSeparator() 84 | } 85 | 86 | self.language = language 87 | contentView.addSubview(pickerView!) 88 | } 89 | 90 | // MARK: - Separator line 91 | func removeAllSeparators() { 92 | for sublayer in layer.sublayers! { 93 | if sublayer.name == TBRPTopSeparatorIdentifier || sublayer.name == TBRPBottomSeparatorIdentifier { 94 | sublayer.removeFromSuperlayer() 95 | } 96 | } 97 | } 98 | 99 | func addBottomSeparatorFromLeftX(leftX: CGFloat) { 100 | let bottomSeparator = CALayer() 101 | bottomSeparator.name = TBRPBottomSeparatorIdentifier 102 | 103 | bottomSeparator.frame = CGRectMake(leftX, TBRPPickerHeight - TBRPSeparatorLineWidth, TBRPScreenWidth - leftX, TBRPSeparatorLineWidth) 104 | bottomSeparator.backgroundColor = TBRPHelper.separatorColor() 105 | 106 | layer.addSublayer(bottomSeparator) 107 | } 108 | 109 | func addDefaultBottomSeparator() { 110 | addBottomSeparatorFromLeftX(TBRPHelper.leadingMargin()) 111 | } 112 | 113 | func addSectionBottomSeparator() { 114 | addBottomSeparatorFromLeftX(0) 115 | } 116 | 117 | // MARK: - UIPickerView data source 118 | func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { 119 | if pickerStyle == .Frequency { 120 | return 1 121 | } else { 122 | return 2 123 | } 124 | } 125 | 126 | func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 127 | if pickerStyle == .Frequency { 128 | return 4 129 | } else if pickerStyle == .Interval { 130 | if component == 0 { 131 | return TBRPPickerMaxRowCount 132 | } else { 133 | return 1 134 | } 135 | } else if pickerStyle == .Week { 136 | if component == 0 { 137 | return TBRPHelper.numbersInWeekPicker(language).count 138 | } else { 139 | return TBRPHelper.daysInWeekPicker(language).count 140 | } 141 | } 142 | return 0 143 | } 144 | 145 | func pickerView(pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { 146 | return TBRPPickerRowHeight 147 | } 148 | 149 | func pickerView(pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { 150 | return TBRPScreenWidth / 2 151 | } 152 | 153 | func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 154 | if pickerStyle == .Frequency { 155 | return TBRPHelper.frequencies(language)[row] 156 | } else if pickerStyle == .Interval { 157 | if component == 0 { 158 | return "\(row + 1)" 159 | } else { 160 | return unit?.lowercaseString 161 | } 162 | } else if pickerStyle == .Week { 163 | if component == 0 { 164 | return TBRPHelper.numbersInWeekPicker(language)[row] 165 | } else { 166 | return TBRPHelper.daysInWeekPicker(language)[row] 167 | } 168 | } 169 | return nil 170 | } 171 | 172 | // MARK: - UIPickerView delegate 173 | func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 174 | if let _ = delegate { 175 | delegate!.pickerDidPick(pickerView, pickStyle: pickerStyle!, didSelectRow: row, inComponent: component) 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPCollectionViewCell.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/25. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum TBRPCollectionMode { 12 | case Days 13 | case Months 14 | } 15 | 16 | let TBRPDaysItemWidth: CGFloat = TBRPScreenWidth / 7.0 17 | let TBRPDaysItemHeight = CGFloat((Int)(TBRPDaysItemWidth * 0.9 + 0.5)) 18 | let TBRPMonthsItemWidth: CGFloat = TBRPScreenWidth / 4.0 19 | let TBRPMonthsItemHeight: CGFloat = 44.0 20 | 21 | let TBRPDaysCollectionHeight: CGFloat = TBRPDaysItemHeight * 5 22 | let TBRPMonthsCollectionHeight: CGFloat = TBRPMonthsItemHeight * 3 23 | 24 | private let TBRPCollectionItemID = "TBRPCollectionItem" 25 | 26 | protocol TBRPCollectionViewCellDelegate { 27 | func selectedMonthdaysDidChanged(days: [Int]) 28 | func selectedMonthsDidChanged(months: [Int]) 29 | } 30 | 31 | class TBRPCollectionViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate { 32 | // MARK: - Public properties 33 | var language: TBRPLanguage = .English 34 | var selectedMonthdays = [Int]() 35 | var selectedMonths = [Int]() 36 | var delegate: TBRPCollectionViewCellDelegate? 37 | 38 | // MARK: - Private properties 39 | private var collectionView: UICollectionView? 40 | private var mode: TBRPCollectionMode? 41 | 42 | // MARK: - View life cycle 43 | override func awakeFromNib() { 44 | super.awakeFromNib() 45 | 46 | } 47 | 48 | override func prepareForReuse() { 49 | super.prepareForReuse() 50 | 51 | removeAllSeparators() 52 | } 53 | 54 | convenience init(style: UITableViewCellStyle, reuseIdentifier: String?, mode: TBRPCollectionMode, language: TBRPLanguage) { 55 | self.init() 56 | 57 | separatorInset = UIEdgeInsetsMake(0, TBRPScreenWidth, 0, 0) 58 | 59 | self.mode = mode 60 | if mode == .Months { 61 | addSectionTopSeparator() 62 | addSectionBottomSeparator() 63 | } 64 | 65 | self.language = language 66 | let layout = TBRPCollectionViewLayout(mode: mode) 67 | 68 | if mode == .Days { 69 | collectionView = UICollectionView(frame: CGRectMake(0, 0, TBRPScreenWidth, TBRPDaysCollectionHeight), collectionViewLayout: layout) 70 | } else { 71 | collectionView = UICollectionView(frame: CGRectMake(0, 0, TBRPScreenWidth, TBRPMonthsCollectionHeight), collectionViewLayout: layout) 72 | } 73 | 74 | if let _ = collectionView { 75 | collectionView?.dataSource = self 76 | collectionView?.delegate = self 77 | collectionView?.registerClass(TBRPCollectionItem.self, forCellWithReuseIdentifier: TBRPCollectionItemID) 78 | collectionView?.backgroundColor = UIColor.clearColor() 79 | contentView.addSubview(collectionView!) 80 | } 81 | backgroundColor = UIColor.clearColor() 82 | } 83 | 84 | // MARK: - Separator line 85 | func removeAllSeparators() { 86 | for sublayer in layer.sublayers! { 87 | if sublayer.name == TBRPTopSeparatorIdentifier || sublayer.name == TBRPBottomSeparatorIdentifier { 88 | sublayer.removeFromSuperlayer() 89 | } 90 | } 91 | } 92 | 93 | func addBottomSeparatorFromLeftX(leftX: CGFloat) { 94 | let bottomSeparator = CALayer() 95 | bottomSeparator.name = TBRPBottomSeparatorIdentifier 96 | 97 | bottomSeparator.frame = CGRectMake(leftX, TBRPMonthsCollectionHeight - TBRPSeparatorLineWidth, TBRPScreenWidth - leftX, TBRPSeparatorLineWidth) 98 | bottomSeparator.backgroundColor = TBRPHelper.separatorColor() 99 | 100 | layer.addSublayer(bottomSeparator) 101 | } 102 | 103 | func addTopSeparatorFromLeftX(leftX: CGFloat) { 104 | let topSeparator = CALayer() 105 | topSeparator.name = TBRPTopSeparatorIdentifier 106 | 107 | topSeparator.frame = CGRectMake(leftX, 0,TBRPScreenWidth - leftX, TBRPSeparatorLineWidth) 108 | topSeparator.backgroundColor = TBRPHelper.separatorColor() 109 | 110 | layer.addSublayer(topSeparator) 111 | } 112 | 113 | func addSectionTopSeparator() { 114 | addTopSeparatorFromLeftX(0) 115 | } 116 | 117 | func addSectionBottomSeparator() { 118 | addBottomSeparatorFromLeftX(0) 119 | } 120 | 121 | // MARK: - UICollectionView data source 122 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 123 | return 1 124 | } 125 | 126 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 127 | if mode == .Days { 128 | return 31 129 | } else { 130 | return 12 131 | } 132 | } 133 | 134 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 135 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(TBRPCollectionItemID, forIndexPath: indexPath) as! TBRPCollectionItem 136 | cell.tintColor = tintColor 137 | 138 | if mode == .Days { 139 | let day = indexPath.row + 1 140 | 141 | cell.textLabel!.text = "\(day)" 142 | cell.setItemSelected(selectedMonthdays.contains(day)) 143 | } else { 144 | let month = indexPath.row + 1 145 | 146 | cell.textLabel!.text = TBRPHelper.yearMonths(language)[indexPath.row] 147 | cell.setItemSelected(selectedMonths.contains(month)) 148 | } 149 | 150 | configureSeparatorLine(cell, indexPath: indexPath) 151 | 152 | return cell 153 | } 154 | 155 | func configureSeparatorLine(cell: TBRPCollectionItem,indexPath: NSIndexPath) { 156 | if mode == .Days { 157 | cell.showTopLine = false 158 | cell.showLeftLine = false 159 | 160 | if (indexPath.row + 1) % 7 == 0 { 161 | cell.showRightLine = false 162 | } 163 | 164 | } else if mode == .Months { 165 | cell.showBottomLine = false 166 | if indexPath.row + 1 < 5 { 167 | cell.showTopLine = false 168 | } 169 | 170 | cell.showLeftLine = false 171 | if (indexPath.row + 1) % 4 == 0 { 172 | cell.showRightLine = false 173 | } 174 | } 175 | } 176 | 177 | // MARK: - UICollectionView delegate 178 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 179 | let cell = collectionView.cellForItemAtIndexPath(indexPath) as! TBRPCollectionItem 180 | 181 | if mode == .Days { 182 | let day = indexPath.row + 1 183 | if selectedMonthdays.count == 1 && selectedMonthdays.contains(day) == true { 184 | return 185 | } 186 | 187 | cell.setItemSelected(!selectedMonthdays.contains(day)) 188 | 189 | if selectedMonthdays.contains(day) == true { 190 | selectedMonthdays.removeObject(day) 191 | } else { 192 | selectedMonthdays.append(day) 193 | } 194 | cell.backgroundColor = UIColor.whiteColor() 195 | 196 | if let _ = delegate { 197 | delegate?.selectedMonthdaysDidChanged(selectedMonthdays) 198 | } 199 | } else if mode == .Months { 200 | let month = indexPath.row + 1 201 | if selectedMonths.count == 1 && selectedMonths.contains(month) == true { 202 | return 203 | } 204 | 205 | cell.setItemSelected(!selectedMonths.contains(month)) 206 | 207 | if selectedMonths.contains(month) == true { 208 | selectedMonths.removeObject(month) 209 | } else { 210 | selectedMonths.append(month) 211 | } 212 | 213 | if let _ = delegate { 214 | delegate?.selectedMonthsDidChanged(selectedMonths) 215 | } 216 | } 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPPresetRepeatController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPPresetRepeatController.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/23. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let TBRPPresetRepeatCellID = "TBRPPresetRepeatCell" 12 | 13 | @objc protocol TBRepeatPickerDelegate { 14 | func didPickRecurrence(recurrence: TBRecurrence?, repeatPicker: TBRepeatPicker) 15 | } 16 | 17 | class TBRPPresetRepeatController: UITableViewController, TBRPCustomRepeatControllerDelegate { 18 | // MARK: - Public properties 19 | var occurrenceDate = NSDate() 20 | var tintColor = UIColor.blueColor() 21 | var language: TBRPLanguage = .English 22 | var delegate: TBRepeatPickerDelegate? 23 | 24 | var recurrence: TBRecurrence? { 25 | didSet { 26 | setupSelectedIndexPath(recurrence) 27 | } 28 | } 29 | var selectedIndexPath = NSIndexPath(forRow: 0, inSection: 0) 30 | 31 | // MARK: - Private properties 32 | private var recurrenceBackup: TBRecurrence? 33 | private var presetRepeats = [String]() 34 | private var internationalControl: TBRPInternationalControl? 35 | 36 | // MARK: - View life cycle 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | commonInit() 41 | } 42 | 43 | private func commonInit() { 44 | internationalControl = TBRPInternationalControl(language: language) 45 | navigationItem.title = internationalControl?.localized("TBRPPresetRepeatController.navigation.title", comment: "Repeat") 46 | 47 | navigationController?.navigationBar.tintColor = tintColor 48 | tableView.tintColor = tintColor 49 | 50 | presetRepeats = TBRPHelper.presetRepeats(language) 51 | 52 | if let _ = recurrence { 53 | recurrenceBackup = recurrence!.recurrenceCopy() 54 | } 55 | } 56 | 57 | override func didMoveToParentViewController(parent: UIViewController?) { 58 | if parent == nil { 59 | // navigation was popped 60 | if TBRecurrence.isEqualRecurrence(recurrence, recurrence2: recurrenceBackup) == false { 61 | if let _ = delegate { 62 | delegate?.didPickRecurrence(recurrence, repeatPicker: self as! TBRepeatPicker) 63 | } 64 | } 65 | } 66 | } 67 | 68 | // MARK: - Helper 69 | private func setupSelectedIndexPath(recurrence: TBRecurrence?) { 70 | if recurrence == nil { 71 | selectedIndexPath = NSIndexPath(forRow: 0, inSection: 0) 72 | } else if recurrence?.isDailyRecurrence() == true { 73 | selectedIndexPath = NSIndexPath(forRow: 1, inSection: 0) 74 | } else if recurrence?.isWeeklyRecurrence(occurrenceDate) == true { 75 | selectedIndexPath = NSIndexPath(forRow: 2, inSection: 0) 76 | } else if recurrence?.isBiWeeklyRecurrence(occurrenceDate) == true { 77 | selectedIndexPath = NSIndexPath(forRow: 3, inSection: 0) 78 | } else if recurrence?.isMonthlyRecurrence(occurrenceDate) == true { 79 | selectedIndexPath = NSIndexPath(forRow: 4, inSection: 0) 80 | } else if recurrence?.isYearlyRecurrence(occurrenceDate) == true { 81 | selectedIndexPath = NSIndexPath(forRow: 5, inSection: 0) 82 | } else { 83 | selectedIndexPath = NSIndexPath(forRow: 0, inSection: 1) 84 | } 85 | } 86 | 87 | private func updateRecurrence(indexPath: NSIndexPath) { 88 | if indexPath.section == 1 { 89 | return 90 | } 91 | 92 | switch indexPath.row { 93 | case 0: 94 | recurrence = nil 95 | 96 | case 1: 97 | recurrence = TBRecurrence.dailyRecurrence(occurrenceDate) 98 | 99 | case 2: 100 | recurrence = TBRecurrence.weeklyRecurrence(occurrenceDate) 101 | 102 | case 3: 103 | recurrence = TBRecurrence.biWeeklyRecurrence(occurrenceDate) 104 | 105 | case 4: 106 | recurrence = TBRecurrence.monthlyRecurrence(occurrenceDate) 107 | 108 | case 5: 109 | recurrence = TBRecurrence.yearlyRecurrence(occurrenceDate) 110 | 111 | default: 112 | break 113 | } 114 | } 115 | 116 | private func updateFooterTitle() { 117 | let footerView = tableView.footerViewForSection(1) 118 | 119 | tableView.beginUpdates() 120 | footerView?.textLabel?.text = footerTitle() 121 | tableView.endUpdates() 122 | footerView?.setNeedsLayout() 123 | } 124 | 125 | private func footerTitle() -> String? { 126 | if let _ = recurrence { 127 | if selectedIndexPath.section == 0 { 128 | return nil 129 | } 130 | return TBRPHelper.recurrenceString(recurrence!, occurrenceDate: occurrenceDate, language: language) 131 | } 132 | return nil 133 | } 134 | 135 | // MARK: - Table view data source 136 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 137 | return 2 138 | } 139 | 140 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 141 | if section == 0 { 142 | return presetRepeats.count 143 | } else { 144 | return 1 145 | } 146 | } 147 | 148 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 149 | return 44.0 150 | } 151 | 152 | override func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { 153 | if section == 1 && recurrence != nil { 154 | return footerTitle() 155 | } 156 | return nil 157 | } 158 | 159 | override func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { 160 | if view.isKindOfClass(UITableViewHeaderFooterView) { 161 | let tableViewHeaderFooterView = view as! UITableViewHeaderFooterView 162 | tableViewHeaderFooterView.textLabel?.font = UIFont.systemFontOfSize(CGFloat(13.0)) 163 | } 164 | } 165 | 166 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 167 | var cell = tableView.dequeueReusableCellWithIdentifier(TBRPPresetRepeatCellID) 168 | if cell == nil { 169 | cell = UITableViewCell(style: .Default, reuseIdentifier: TBRPPresetRepeatCellID) 170 | } 171 | 172 | if indexPath.section == 1 { 173 | cell?.accessoryType = .DisclosureIndicator 174 | cell?.textLabel?.text = internationalControl?.localized("TBRPPresetRepeatController.textLabel.custom", comment: "Custom") 175 | } else { 176 | cell?.accessoryType = .None 177 | cell?.textLabel?.text = presetRepeats[indexPath.row] 178 | } 179 | 180 | cell?.imageView?.image = UIImage(named: "TBRP-Checkmark")?.imageWithRenderingMode(.AlwaysTemplate) 181 | 182 | if indexPath == selectedIndexPath { 183 | cell?.imageView?.hidden = false 184 | } else { 185 | cell?.imageView?.hidden = true 186 | } 187 | 188 | return cell! 189 | } 190 | 191 | // MARK: - Table view delegate 192 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 193 | let lastSelectedCell = tableView.cellForRowAtIndexPath(selectedIndexPath) 194 | let currentSelectedCell = tableView.cellForRowAtIndexPath(indexPath) 195 | 196 | lastSelectedCell?.imageView?.hidden = true 197 | currentSelectedCell?.imageView?.hidden = false 198 | 199 | selectedIndexPath = indexPath 200 | 201 | if indexPath.section == 1 { 202 | let customRepeatController = TBRPCustomRepeatController(style: .Grouped) 203 | customRepeatController.occurrenceDate = occurrenceDate 204 | customRepeatController.tintColor = tintColor 205 | customRepeatController.language = language 206 | 207 | if let _ = recurrence { 208 | customRepeatController.recurrence = recurrence! 209 | } else { 210 | customRepeatController.recurrence = TBRecurrence.dailyRecurrence(occurrenceDate) 211 | } 212 | customRepeatController.delegate = self 213 | 214 | navigationController?.pushViewController(customRepeatController, animated: true) 215 | } else { 216 | updateRecurrence(indexPath) 217 | updateFooterTitle() 218 | 219 | navigationController?.popViewControllerAnimated(true) 220 | } 221 | 222 | tableView.deselectRowAtIndexPath(indexPath, animated: true) 223 | } 224 | 225 | // MARK: - TBRPCustomRepeatController delegate 226 | func didFinishPickingCustomRecurrence(recurrence: TBRecurrence) { 227 | self.recurrence = recurrence 228 | updateFooterTitle() 229 | tableView.reloadData() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRecurrence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRecurrence.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by 洪鑫 on 15/10/7. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc enum TBRPFrequency: Int { 12 | case Daily = 0 13 | case Weekly = 1 14 | case Monthly = 2 15 | case Yearly = 3 16 | } 17 | 18 | @objc enum TBRPWeekPickerNumber: Int { 19 | case First = 0 20 | case Second = 1 21 | case Third = 2 22 | case Fourth = 3 23 | case Fifth = 4 24 | case Last = 5 25 | } 26 | 27 | @objc enum TBRPWeekPickerDay: Int { 28 | case Sunday = 0 29 | case Monday = 1 30 | case Tuesday = 2 31 | case Wednesday = 3 32 | case Thursday = 4 33 | case Friday = 5 34 | case Saturday = 6 35 | case Day = 7 36 | case Weekday = 8 37 | case WeekendDay = 9 38 | } 39 | 40 | class TBRecurrence: NSObject { 41 | /** The frequency of recurrence, must be one of the following cases: 42 | * TBRPFrequency.Daily 43 | * TBRPFrequency.Weekly 44 | * TBRPFrequency.Monthly 45 | * TBRPFrequency.Yearly 46 | */ 47 | var frequency: TBRPFrequency = .Daily 48 | 49 | /** The interval between each frequency iteration. For example, when in a daily frequency, an interval of 1 means that event will occur every day, and an interval of 3 means that event will occur every 3 days. The default interval is 1. 50 | */ 51 | var interval: Int = 1 52 | 53 | /** The selected weekdays when frequency is weekly. Elements in this array are all integers in a range between 0 to 6, 0 means Sunday, which is the first day of week, and the integers from 1 to 6 respectively mean the days from Monday to Saturday. This property value is valid only for a frequency type of TBRPFrequency.Weekly. 54 | */ 55 | var selectedWeekdays = [Int]() { 56 | didSet { 57 | selectedWeekdays = selectedWeekdays.sort { $0 < $1 } 58 | } 59 | } 60 | 61 | /** A boolean value decides whether the recurrence is constructed by week number or not when frequency is weekly or yearly. For example, we can get a recurrence like "Second Friday" with it. This property value is valid only for a frequency type of TBRPFrequency.Monthly or TBRPFrequency.Yearly. 62 | */ 63 | var byWeekNumber = false 64 | 65 | /** The selected monthdays when frequency is monthly. Elements in this array are all integers in a range between 1 to 31, which respectively mean the days from 1st to 31st of a month. This property value is valid only for a frequency type of TBRPFrequency.Monthly. 66 | */ 67 | var selectedMonthdays = [Int]() { 68 | didSet { 69 | selectedMonthdays = selectedMonthdays.sort { $0 < $1 } 70 | } 71 | } 72 | 73 | /** The selected months when frequency is yearly. Elements in this array are all integers in a range between 1 to 12, which respectively mean the months from January to December. This property value is valid only for a frequency type of TBRPFrequency.Yearly. 74 | */ 75 | var selectedMonths = [Int]() { 76 | didSet { 77 | selectedMonths = selectedMonths.sort { $0 < $1 } 78 | } 79 | } 80 | 81 | /** The week number when the recurrence is constructed by week number, must be one of the following cases: 82 | * TBRPWeekPickerNumber.First 83 | * TBRPWeekPickerNumber.Second 84 | * TBRPWeekPickerNumber.Third 85 | * TBRPWeekPickerNumber.Fourth 86 | * TBRPWeekPickerNumber.Fifth 87 | * TBRPWeekPickerNumber.Last 88 | 89 | This property value is valid only for a frequency type of TBRPFrequency.Monthly or TBRPFrequency.Yearly. 90 | */ 91 | var pickedWeekNumber: TBRPWeekPickerNumber = .First 92 | 93 | /** The day of week when the recurrence is constructed by week number, must be one of the following cases: 94 | * TBRPWeekPickerDay.Sunday 95 | * TBRPWeekPickerDay.Monday 96 | * TBRPWeekPickerDay.Tuesday 97 | * TBRPWeekPickerDay.Wednesday 98 | * TBRPWeekPickerDay.Thursday 99 | * TBRPWeekPickerDay.Friday 100 | * TBRPWeekPickerDay.Saturday 101 | * TBRPWeekPickerDay.Day 102 | * TBRPWeekPickerDay.Weekday 103 | * TBRPWeekPickerDay.WeekendDay 104 | 105 | This property value is valid only for a frequency type of TBRPFrequency.Monthly or TBRPFrequency.Yearly. 106 | */ 107 | var pickedWeekday: TBRPWeekPickerDay = .Sunday 108 | 109 | // MARK: - Initialization 110 | convenience init(occurrenceDate: NSDate) { 111 | self.init() 112 | 113 | let occurrenceDateDayIndexInWeek = NSCalendar.dayIndexInWeek(occurrenceDate) 114 | let occurrenceDateDayIndexInMonth = NSCalendar.dayIndexInMonth(occurrenceDate) 115 | let occurrenceDateMonthIndexInYear = NSCalendar.monthIndexInYear(occurrenceDate) 116 | 117 | selectedWeekdays = [occurrenceDateDayIndexInWeek - 1] 118 | selectedMonthdays = [occurrenceDateDayIndexInMonth] 119 | selectedMonths = [occurrenceDateMonthIndexInYear] 120 | } 121 | 122 | func recurrenceCopy() -> TBRecurrence { 123 | let recurrence = TBRecurrence() 124 | 125 | recurrence.frequency = frequency 126 | recurrence.interval = interval 127 | recurrence.selectedWeekdays = selectedWeekdays 128 | recurrence.byWeekNumber = byWeekNumber 129 | recurrence.selectedMonthdays = selectedMonthdays 130 | recurrence.selectedMonths = selectedMonths 131 | recurrence.pickedWeekNumber = pickedWeekNumber 132 | recurrence.pickedWeekday = pickedWeekday 133 | 134 | return recurrence 135 | } 136 | 137 | class func isEqualRecurrence(recurrence1: TBRecurrence?, recurrence2: TBRecurrence?) -> Bool { 138 | if recurrence1 == nil && recurrence2 == nil { 139 | return true 140 | } else if recurrence1?.frequency == recurrence2?.frequency && recurrence1?.interval == recurrence2?.interval { 141 | if recurrence1?.frequency == .Daily { 142 | return true 143 | } else if recurrence1?.frequency == .Weekly { 144 | let selectedWeekdays1 = recurrence1?.selectedWeekdays.sort { $0 < $1 } 145 | let selectedWeekdays2 = recurrence2?.selectedWeekdays.sort { $0 < $1 } 146 | 147 | return selectedWeekdays1! == selectedWeekdays2! 148 | } else if recurrence1?.frequency == .Monthly { 149 | if recurrence1?.byWeekNumber == recurrence2?.byWeekNumber { 150 | if recurrence1?.byWeekNumber == true { 151 | return recurrence1?.pickedWeekNumber == recurrence2?.pickedWeekNumber && recurrence1?.pickedWeekday == recurrence2?.pickedWeekday 152 | } else { 153 | let selectedMonthdays1 = recurrence1?.selectedMonthdays.sort { $0 < $1 } 154 | let selectedMonthdays2 = recurrence2?.selectedMonthdays.sort { $0 < $1 } 155 | 156 | return selectedMonthdays1! == selectedMonthdays2! 157 | } 158 | } else { 159 | return false 160 | } 161 | } else if recurrence1?.frequency == .Yearly { 162 | if recurrence1?.byWeekNumber == recurrence2?.byWeekNumber { 163 | let selectedMonths1 = recurrence1?.selectedMonths.sort { $0 < $1 } 164 | let selectedMonths2 = recurrence2?.selectedMonths.sort { $0 < $1 } 165 | 166 | if recurrence1?.byWeekNumber == true { 167 | return selectedMonths1! == selectedMonths2! && recurrence1?.pickedWeekNumber == recurrence2?.pickedWeekNumber && recurrence1?.pickedWeekday == recurrence2?.pickedWeekday 168 | } else { 169 | return selectedMonths1! == selectedMonths2! 170 | } 171 | } else { 172 | return false 173 | } 174 | } else { 175 | return false 176 | } 177 | } else { 178 | return false 179 | } 180 | } 181 | 182 | // preset recurrence initialization 183 | class func dailyRecurrence(occurrenceDate: NSDate) -> TBRecurrence { 184 | return initDaily(1, occurrenceDate: occurrenceDate) 185 | } 186 | 187 | class func weeklyRecurrence(occurrenceDate: NSDate) -> TBRecurrence { 188 | return initWeekly(1, selectedWeekdays: [], occurrenceDate: occurrenceDate) 189 | } 190 | 191 | class func biWeeklyRecurrence(occurrenceDate: NSDate) -> TBRecurrence { 192 | return initWeekly(2, selectedWeekdays: [], occurrenceDate: occurrenceDate) 193 | } 194 | 195 | class func monthlyRecurrence(occurrenceDate: NSDate) -> TBRecurrence { 196 | return initMonthly(1, selectedMonthdays: [], occurrenceDate: occurrenceDate) 197 | } 198 | 199 | class func yearlyRecurrence(occurrenceDate: NSDate) -> TBRecurrence { 200 | return initYearly(1, selectedMonths: [], occurrenceDate: occurrenceDate) 201 | } 202 | 203 | class func weekdayRecurrence(occurrenceDate: NSDate) -> TBRecurrence { 204 | return initWeekly(1, selectedWeekdays: [1, 2, 3, 4, 5], occurrenceDate: occurrenceDate) 205 | } 206 | 207 | // custom recurrence initialization 208 | class func initDaily(interval: Int, occurrenceDate: NSDate) -> TBRecurrence { 209 | let dailyRecurrence = TBRecurrence(occurrenceDate: occurrenceDate) 210 | dailyRecurrence.frequency = .Daily 211 | 212 | if interval < 1 { 213 | dailyRecurrence.interval = 1 214 | } else { 215 | dailyRecurrence.interval = interval 216 | } 217 | 218 | return dailyRecurrence 219 | } 220 | 221 | class func initWeekly(interval: Int, var selectedWeekdays: [Int], occurrenceDate: NSDate) -> TBRecurrence { 222 | let weeklyRecurrence = TBRecurrence(occurrenceDate: occurrenceDate) 223 | weeklyRecurrence.frequency = .Weekly 224 | for day in selectedWeekdays { 225 | if day < 0 || day > 6 { 226 | selectedWeekdays.removeObject(day) 227 | } 228 | } 229 | selectedWeekdays = selectedWeekdays.unique 230 | 231 | if interval < 1 { 232 | weeklyRecurrence.interval = 1 233 | } else { 234 | weeklyRecurrence.interval = interval 235 | } 236 | 237 | if selectedWeekdays.count > 0 { 238 | weeklyRecurrence.selectedWeekdays = selectedWeekdays 239 | } 240 | 241 | return weeklyRecurrence 242 | } 243 | 244 | class func initMonthly(interval: Int, var selectedMonthdays: [Int], occurrenceDate: NSDate) -> TBRecurrence { 245 | let monthlyRecurrence = TBRecurrence(occurrenceDate: occurrenceDate) 246 | monthlyRecurrence.frequency = .Monthly 247 | monthlyRecurrence.byWeekNumber = false 248 | for day in selectedMonthdays { 249 | if day < 1 || day > 31 { 250 | selectedMonthdays.removeObject(day) 251 | } 252 | } 253 | selectedMonthdays = selectedMonthdays.unique 254 | 255 | if interval < 1 { 256 | monthlyRecurrence.interval = 1 257 | } else { 258 | monthlyRecurrence.interval = interval 259 | } 260 | 261 | if selectedMonthdays.count > 0 { 262 | monthlyRecurrence.selectedMonthdays = selectedMonthdays 263 | } 264 | 265 | return monthlyRecurrence 266 | } 267 | 268 | class func initMonthlyByWeekNumber(interval: Int, pickedWeekNumber: TBRPWeekPickerNumber, pickedWeekday: TBRPWeekPickerDay, occurrenceDate: NSDate) -> TBRecurrence { 269 | let monthlyRecurrence = TBRecurrence(occurrenceDate: occurrenceDate) 270 | monthlyRecurrence.frequency = .Monthly 271 | monthlyRecurrence.byWeekNumber = true 272 | 273 | if interval < 1 { 274 | monthlyRecurrence.interval = 1 275 | } else { 276 | monthlyRecurrence.interval = interval 277 | } 278 | 279 | monthlyRecurrence.pickedWeekNumber = pickedWeekNumber 280 | monthlyRecurrence.pickedWeekday = pickedWeekday 281 | 282 | return monthlyRecurrence 283 | } 284 | 285 | class func initYearly(interval: Int, var selectedMonths: [Int], occurrenceDate: NSDate) -> TBRecurrence { 286 | let yearlyRecurrence = TBRecurrence(occurrenceDate: occurrenceDate) 287 | yearlyRecurrence.frequency = .Yearly 288 | yearlyRecurrence.byWeekNumber = false 289 | for day in selectedMonths { 290 | if day < 1 || day > 12 { 291 | selectedMonths.removeObject(day) 292 | } 293 | } 294 | selectedMonths = selectedMonths.unique 295 | 296 | if interval < 1 { 297 | yearlyRecurrence.interval = 1 298 | } else { 299 | yearlyRecurrence.interval = interval 300 | } 301 | 302 | if selectedMonths.count > 0 { 303 | yearlyRecurrence.selectedMonths = selectedMonths 304 | } 305 | 306 | return yearlyRecurrence 307 | } 308 | 309 | class func initYearlyByWeekNumber(interval: Int, pickedWeekNumber: TBRPWeekPickerNumber, pickedWeekday: TBRPWeekPickerDay, occurrenceDate: NSDate) -> TBRecurrence { 310 | let yearlyRecurrence = TBRecurrence(occurrenceDate: occurrenceDate) 311 | yearlyRecurrence.frequency = .Yearly 312 | yearlyRecurrence.byWeekNumber = true 313 | 314 | if interval < 1 { 315 | yearlyRecurrence.interval = 1 316 | } else { 317 | yearlyRecurrence.interval = interval 318 | } 319 | 320 | yearlyRecurrence.pickedWeekNumber = pickedWeekNumber 321 | yearlyRecurrence.pickedWeekday = pickedWeekday 322 | 323 | return yearlyRecurrence 324 | } 325 | 326 | // MARK: - Helper 327 | func isDailyRecurrence() -> Bool { 328 | return frequency == .Daily && interval == 1 329 | } 330 | 331 | func isWeeklyRecurrence(occurrenceDate: NSDate) -> Bool { 332 | let occurrenceDateDayIndexInWeek = NSCalendar.dayIndexInWeek(occurrenceDate) 333 | 334 | return frequency == .Weekly && selectedWeekdays == [occurrenceDateDayIndexInWeek - 1] && interval == 1 335 | } 336 | 337 | func isBiWeeklyRecurrence(occurrenceDate: NSDate) -> Bool { 338 | let occurrenceDateDayIndexInWeek = NSCalendar.dayIndexInWeek(occurrenceDate) 339 | 340 | return frequency == .Weekly && selectedWeekdays == [occurrenceDateDayIndexInWeek - 1] && interval == 2 341 | } 342 | 343 | func isMonthlyRecurrence(occurrenceDate: NSDate) -> Bool { 344 | let occurrenceDateDayIndexInMonth = NSCalendar.dayIndexInMonth(occurrenceDate) 345 | 346 | return frequency == .Monthly && interval == 1 && byWeekNumber == false && selectedMonthdays == [occurrenceDateDayIndexInMonth] 347 | } 348 | 349 | func isYearlyRecurrence(occurrenceDate: NSDate) -> Bool { 350 | let occurrenceDateMonthIndexInYear = NSCalendar.monthIndexInYear(occurrenceDate) 351 | 352 | return frequency == .Yearly && interval == 1 && byWeekNumber == false && selectedMonths == [occurrenceDateMonthIndexInYear] 353 | } 354 | 355 | func isWeekdayRecurrence() -> Bool { 356 | return frequency == .Weekly && interval == 1 && selectedWeekdays == [1, 2, 3, 4, 5] 357 | } 358 | 359 | func isCustomRecurrence(occurrenceDate: NSDate) -> Bool { 360 | return !isDailyRecurrence() && !isWeeklyRecurrence(occurrenceDate) && !isBiWeeklyRecurrence(occurrenceDate) && !isMonthlyRecurrence(occurrenceDate) && !isYearlyRecurrence(occurrenceDate) 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /TBRepeatPicker/TBRPHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TBRPHelper.swift 3 | // TBRepeatPicker 4 | // 5 | // Created by hongxin on 15/9/28. 6 | // Copyright © 2015年 Teambition. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let TBRPScreenWidth: CGFloat = UIScreen.mainScreen().bounds.size.width 12 | let TBRPScreenHeight: CGFloat = UIScreen.mainScreen().bounds.size.height 13 | let iPhone6PlusScreenWidth: CGFloat = 414.0 14 | 15 | let TBRPTopSeparatorIdentifier = "TBRPTopSeparator" 16 | let TBRPBottomSeparatorIdentifier = "TBRPBottomSeparator" 17 | let TBRPSeparatorLineWidth: CGFloat = 0.5 18 | 19 | class TBRPHelper { 20 | class func leadingMargin() -> CGFloat { 21 | if TBRPScreenWidth == iPhone6PlusScreenWidth { 22 | return 20.0 23 | } 24 | return 15.0 25 | } 26 | 27 | class func separatorColor() -> CGColorRef { 28 | let defaultSeparatorColor = UITableView().separatorColor 29 | let red = defaultSeparatorColor?.rgbComponents.red 30 | let green = defaultSeparatorColor?.rgbComponents.green 31 | let blue = defaultSeparatorColor?.rgbComponents.blue 32 | let bit: CGFloat = 255.0 33 | 34 | return UIColor.init(red: red! / bit, green: green! / bit, blue: blue! / bit, alpha: 0.7).CGColor 35 | } 36 | 37 | class func detailTextColor() -> UIColor { 38 | return UIColor.grayColor() 39 | } 40 | 41 | class func weekdays(language: TBRPLanguage) -> [String] { 42 | let languageLocale = NSLocale(localeIdentifier: TBRPInternationalControl.languageKey(language)) 43 | 44 | let dateFormatter = NSDateFormatter() 45 | dateFormatter.locale = languageLocale 46 | return dateFormatter.weekdaySymbols 47 | } 48 | 49 | class func yearMonths(language: TBRPLanguage) -> [String] { 50 | let languageLocale = NSLocale(localeIdentifier: TBRPInternationalControl.languageKey(language)) 51 | 52 | let dateFormatter = NSDateFormatter() 53 | dateFormatter.locale = languageLocale 54 | return dateFormatter.shortMonthSymbols 55 | } 56 | 57 | class func completeYearMonths(language: TBRPLanguage) -> [String] { 58 | let languageLocale = NSLocale(localeIdentifier: TBRPInternationalControl.languageKey(language)) 59 | 60 | let dateFormatter = NSDateFormatter() 61 | dateFormatter.locale = languageLocale 62 | return dateFormatter.monthSymbols 63 | } 64 | 65 | class func englishDayString(day: Int) -> String { 66 | var suffix = "th" 67 | let ones = day % 10 68 | let tens = (day / 10) % 10 69 | 70 | if (tens == 1) { 71 | suffix = "th" 72 | } else if (ones == 1) { 73 | suffix = "st" 74 | } else if (ones == 2) { 75 | suffix = "nd" 76 | } else if (ones == 3) { 77 | suffix = "rd" 78 | } else { 79 | suffix = "th" 80 | } 81 | 82 | return "\(day)\(suffix)" 83 | } 84 | 85 | class func frequencies(language: TBRPLanguage) -> [String] { 86 | let internationalControl = TBRPInternationalControl(language: language) 87 | 88 | return [internationalControl.localized("TBRPHelper.frequencies.daily", comment: "Daily"),internationalControl.localized("TBRPHelper.frequencies.weekly", comment: "Weekly"), internationalControl.localized("TBRPHelper.frequencies.monthly", comment: "Monthly"), internationalControl.localized("TBRPHelper.frequencies.yearly", comment: "Yearly")] 89 | } 90 | 91 | class func units(language: TBRPLanguage) -> [String] { 92 | let internationalControl = TBRPInternationalControl(language: language) 93 | 94 | return [internationalControl.localized("TBRPHelper.units.day", comment: "Day"), internationalControl.localized("TBRPHelper.units.week", comment: "Week"), internationalControl.localized("TBRPHelper.units.month", comment: "Month"), internationalControl.localized("TBRPHelper.units.year", comment: "Year")] 95 | } 96 | 97 | class func pluralUnits(language: TBRPLanguage) -> [String] { 98 | let internationalControl = TBRPInternationalControl(language: language) 99 | 100 | return [internationalControl.localized("TBRPHelper.pluralUnits.days", comment: "days"), internationalControl.localized("TBRPHelper.pluralUnits.weeks", comment: "weeks"), internationalControl.localized("TBRPHelper.pluralUnits.months", comment: "months"), internationalControl.localized("TBRPHelper.pluralUnits.years", comment: "years")] 101 | } 102 | 103 | class func presetRepeats(language: TBRPLanguage) -> [String] { 104 | let internationalControl = TBRPInternationalControl(language: language) 105 | 106 | return [internationalControl.localized("TBRPHelper.presetRepeat.never", comment: "Never"), internationalControl.localized("TBRPHelper.presetRepeat.everyDay", comment: "Every Day"), internationalControl.localized("TBRPHelper.presetRepeat.everyWeek", comment: "Every Week"), internationalControl.localized("TBRPHelper.presetRepeat.everyTwoWeeks", comment: "Every 2 Weeks"), internationalControl.localized("TBRPHelper.presetRepeat.everyMonth", comment: "Every Month"), internationalControl.localized("TBRPHelper.presetRepeat.everyYear", comment: "Every Year")] 107 | } 108 | 109 | class func daysInWeekPicker(language: TBRPLanguage) -> [String] { 110 | let internationalControl = TBRPInternationalControl(language: language) 111 | let commonWeekdays = weekdays(language) 112 | let additionDays = [internationalControl.localized("TBRPHelper.daysInWeekPicker.day", comment: "day"), internationalControl.localized("TBRPHelper.daysInWeekPicker.weekday", comment: "weekday"), internationalControl.localized("TBRPHelper.daysInWeekPicker.weekendDay", comment: "weekend day")] 113 | 114 | return commonWeekdays + additionDays 115 | } 116 | 117 | class func numbersInWeekPicker(language: TBRPLanguage) -> [String] { 118 | let internationalControl = TBRPInternationalControl(language: language) 119 | 120 | return [internationalControl.localized("TBRPHelper.numbersInWeekPicker.first", comment: "first"), internationalControl.localized("TBRPHelper.numbersInWeekPicker.second", comment: "second"), internationalControl.localized("TBRPHelper.numbersInWeekPicker.third", comment: "third"), internationalControl.localized("TBRPHelper.numbersInWeekPicker.fourth", comment: "fourth"), internationalControl.localized("TBRPHelper.numbersInWeekPicker.fifth", comment: "fifth"), internationalControl.localized("TBRPHelper.numbersInWeekPicker.last", comment: "last")] 121 | } 122 | 123 | class func recurrenceString(recurrence: TBRecurrence, occurrenceDate: NSDate, language: TBRPLanguage) -> String? { 124 | let internationalControl = TBRPInternationalControl(language: language) 125 | 126 | var unitString: String? 127 | if language == .Korean || language == .Japanese { 128 | unitString = "\(recurrence.interval)" + pluralUnits(language)[recurrence.frequency.rawValue] 129 | } else { 130 | if recurrence.interval == 1 { 131 | unitString = units(language)[recurrence.frequency.rawValue] 132 | } else if recurrence.interval > 1 { 133 | if language == .English { 134 | unitString = "\(recurrence.interval)" + " " + pluralUnits(language)[recurrence.frequency.rawValue] 135 | } else { 136 | unitString = "\(recurrence.interval)" + pluralUnits(language)[recurrence.frequency.rawValue] 137 | } 138 | } 139 | } 140 | 141 | unitString = unitString?.lowercaseString 142 | 143 | if unitString == nil { 144 | return nil 145 | } 146 | 147 | if recurrence.frequency == .Daily { 148 | // Daily 149 | return String(format: internationalControl.localized("RecurrenceString.presetRepeat", comment: "Event will occur every %@."), unitString!) 150 | } else if recurrence.frequency == .Weekly { 151 | // Weekly 152 | let occurrenceDateDayIndexInWeek = NSCalendar.dayIndexInWeek(occurrenceDate) 153 | 154 | if recurrence.selectedWeekdays == [occurrenceDateDayIndexInWeek - 1] { 155 | return String(format: internationalControl.localized("RecurrenceString.presetRepeat", comment: "Event will occur every %@."), unitString!) 156 | } else if recurrence.isWeekdayRecurrence() { 157 | return internationalControl.localized("RecurrenceString.weekdayRecurrence", comment: "Event will occur every weekday.") 158 | } else if recurrence.selectedWeekdays == [0, 1, 2, 3, 4, 5, 6] && recurrence.interval == 1 { 159 | return recurrenceString(TBRecurrence.dailyRecurrence(occurrenceDate), occurrenceDate: occurrenceDate, language: language) 160 | } else { 161 | var weekdaysString: String 162 | if language == .Korean { 163 | weekdaysString = weekdays(language)[recurrence.selectedWeekdays.first!] 164 | } else { 165 | weekdaysString = internationalControl.localized("RecurrenceString.element.on.weekly", comment: "on") + " " + weekdays(language)[recurrence.selectedWeekdays.first!] 166 | } 167 | 168 | for i in 1.. Bool { 150 | return repeatPickerIndexPath != nil 151 | } 152 | 153 | private func hasWeekPicker() -> Bool { 154 | return weekPickerIndexPath != nil 155 | } 156 | 157 | private func closeRepeatPicker() { 158 | if !hasRepeatPicker() { 159 | return; 160 | } 161 | 162 | tableView.deleteRowsAtIndexPaths([repeatPickerIndexPath!], withRowAnimation: .Fade) 163 | repeatPickerIndexPath = nil 164 | updateDetailTextColor() 165 | } 166 | 167 | private func closeWeekPicker() { 168 | if !hasWeekPicker() { 169 | return; 170 | } 171 | 172 | tableView.deleteRowsAtIndexPaths([weekPickerIndexPath!], withRowAnimation: .Fade) 173 | weekPickerIndexPath = nil 174 | } 175 | 176 | private func isRepeatPickerCell(indexPath: NSIndexPath) -> Bool { 177 | return hasRepeatPicker() && repeatPickerIndexPath == indexPath 178 | } 179 | 180 | private func isWeekPickerCell(indexPath: NSIndexPath) -> Bool { 181 | return hasWeekPicker() && weekPickerIndexPath == indexPath && (frequency == .Monthly || frequency == .Yearly) 182 | } 183 | 184 | private func isMonthsCollectionCell(indexPath: NSIndexPath) -> Bool { 185 | return indexPath == NSIndexPath(forRow: 0, inSection: 1) && frequency == .Yearly 186 | } 187 | 188 | private func isDaysCollectionCell(indexPath: NSIndexPath) -> Bool { 189 | return indexPath == NSIndexPath(forRow: 2, inSection: 1) && frequency == .Monthly 190 | } 191 | 192 | private func setupData() { 193 | // refresh weekPickerIndexPath 194 | if byWeekNumber == true { 195 | if frequency == .Yearly { 196 | weekPickerIndexPath = NSIndexPath(forRow: 1, inSection: 2) 197 | } else if frequency == .Monthly { 198 | weekPickerIndexPath = NSIndexPath(forRow: 2, inSection: 1) 199 | } 200 | } 201 | } 202 | 203 | private func updateFrequencyTitleCell() { 204 | frequencyTitleCell?.detailTextLabel?.text = frequencies[(frequency?.rawValue)!] 205 | } 206 | 207 | private func updateIntervalTitleCell() { 208 | intervalTitleCell?.detailTextLabel?.text = unitString() 209 | 210 | if hasRepeatPicker() && repeatPickerIndexPath == NSIndexPath(forRow: 2, inSection: 0) { 211 | let cell = tableView.cellForRowAtIndexPath(repeatPickerIndexPath!) as! TBRPPickerViewCell 212 | cell.unit = unit() 213 | } 214 | } 215 | 216 | private func updateDetailTextColor() { 217 | if repeatPickerIndexPath == NSIndexPath(forRow: 1, inSection: 0) { 218 | frequencyTitleCell?.detailTextLabel?.textColor = tintColor 219 | } else if repeatPickerIndexPath == NSIndexPath(forRow: 2, inSection: 0) { 220 | intervalTitleCell?.detailTextLabel?.textColor = tintColor 221 | } else { 222 | let detailTextColor = TBRPHelper.detailTextColor() 223 | frequencyTitleCell?.detailTextLabel?.textColor = detailTextColor 224 | intervalTitleCell?.detailTextLabel?.textColor = detailTextColor 225 | } 226 | } 227 | 228 | private func updateMoreOptions() { 229 | if frequency == .Daily { 230 | let deleteRange = NSMakeRange(1, tableView.numberOfSections - 1) 231 | 232 | tableView.beginUpdates() 233 | tableView.deleteSections(NSIndexSet(indexesInRange: deleteRange), withRowAnimation: .Fade) 234 | tableView.endUpdates() 235 | } else if frequency == .Weekly || frequency == .Monthly { 236 | if tableView.numberOfSections == 1 { 237 | tableView.beginUpdates() 238 | tableView.insertSections(NSIndexSet(index: 1), withRowAnimation: .Fade) 239 | tableView.endUpdates() 240 | } else if tableView.numberOfSections == 2 { 241 | tableView.beginUpdates() 242 | tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade) 243 | tableView.endUpdates() 244 | } else if tableView.numberOfSections == 3 { 245 | tableView.beginUpdates() 246 | tableView.deleteSections(NSIndexSet(index: 2), withRowAnimation: .Fade) 247 | tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade) 248 | tableView.endUpdates() 249 | } 250 | } else if frequency == .Yearly { 251 | if tableView.numberOfSections == 1 { 252 | let insertYearOptionsRange = NSMakeRange(1, 2) 253 | tableView.insertSections(NSIndexSet(indexesInRange: insertYearOptionsRange), withRowAnimation: .Fade) 254 | } else if tableView.numberOfSections == 2 { 255 | tableView.beginUpdates() 256 | tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade) 257 | tableView.insertSections(NSIndexSet(index: 2), withRowAnimation: .Fade) 258 | tableView.endUpdates() 259 | } 260 | } 261 | } 262 | 263 | private func updateWeekPickerOptions () { 264 | if frequency == .Monthly { 265 | tableView.beginUpdates() 266 | if hasRepeatPicker() { 267 | closeRepeatPicker() 268 | } 269 | 270 | 271 | weekPickerIndexPath = NSIndexPath(forRow: 2, inSection: 1) 272 | tableView.reloadRowsAtIndexPaths([weekPickerIndexPath!], withRowAnimation: .Fade) 273 | 274 | if byWeekNumber == false { 275 | weekPickerIndexPath = nil 276 | } 277 | 278 | tableView.endUpdates() 279 | } else if frequency == .Yearly { 280 | tableView.beginUpdates() 281 | if hasRepeatPicker() { 282 | closeRepeatPicker() 283 | } 284 | 285 | if byWeekNumber == true { 286 | weekPickerIndexPath = NSIndexPath(forRow: 1, inSection: 2) 287 | tableView.insertRowsAtIndexPaths([weekPickerIndexPath!], withRowAnimation: .Fade) 288 | } else if byWeekNumber == false { 289 | closeWeekPicker() 290 | } 291 | 292 | tableView.endUpdates() 293 | } 294 | 295 | updateIntervalCellBottomSeparator() 296 | } 297 | 298 | private func updateFooterTitle() { 299 | let footerView = tableView.footerViewForSection(0) 300 | 301 | tableView.beginUpdates() 302 | footerView?.textLabel?.text = footerTitle() 303 | tableView.endUpdates() 304 | footerView?.setNeedsLayout() 305 | } 306 | 307 | private func footerTitle() -> String? { 308 | return TBRPHelper.recurrenceString(recurrence, occurrenceDate: occurrenceDate, language: language) 309 | } 310 | 311 | private func unit() -> String? { 312 | if interval == 1 { 313 | return units[(frequency?.rawValue)!] 314 | } else if interval > 1 { 315 | return pluralUnits[(frequency?.rawValue)!] 316 | } else { 317 | return nil 318 | } 319 | } 320 | 321 | private func unitString() -> String? { 322 | if interval == 1 { 323 | return unit() 324 | } else if interval > 1 { 325 | return "\(interval!)" + " " + unit()! 326 | } else { 327 | return nil 328 | } 329 | } 330 | 331 | private func updateIntervalCellBottomSeparator() { 332 | if hasRepeatPicker() && intervalTitleIndexpath!.row == 1 { 333 | intervalTitleCell?.updateBottomSeparatorWithLeftX(TBRPHelper.leadingMargin()) 334 | } else { 335 | intervalTitleCell?.updateBottomSeparatorWithLeftX(0) 336 | } 337 | } 338 | 339 | private func updateYearlyWeekCellBottomSeparator() { 340 | let yearlyWeekCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 2)) as! TBRPSwitchCell 341 | if byWeekNumber == true { 342 | yearlyWeekCell.updateBottomSeparatorWithLeftX(TBRPHelper.leadingMargin()) 343 | } else { 344 | yearlyWeekCell.updateBottomSeparatorWithLeftX(0) 345 | } 346 | } 347 | 348 | // MARK: - Table view data source 349 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 350 | if frequency == .Daily { 351 | return 1 352 | } else if frequency == .Yearly { 353 | return 3 354 | } else { 355 | return 2 356 | } 357 | } 358 | 359 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 360 | if section == 0 { 361 | if hasRepeatPicker() { 362 | return 3 363 | } else { 364 | return 2 365 | } 366 | } else if section == 1 { 367 | if frequency == .Weekly { 368 | return 7 369 | } else if frequency == .Monthly { 370 | return 3 371 | } else if frequency == .Yearly { 372 | return 1 373 | } else { 374 | return 0 375 | } 376 | } else { 377 | if byWeekNumber == true { 378 | return 2 379 | } 380 | return 1 381 | } 382 | } 383 | 384 | override func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { 385 | if section == 0 { 386 | return footerTitle() 387 | } 388 | return nil 389 | } 390 | 391 | override func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { 392 | if view.isKindOfClass(UITableViewHeaderFooterView) { 393 | let tableViewHeaderFooterView = view as! UITableViewHeaderFooterView 394 | tableViewHeaderFooterView.textLabel?.font = UIFont.systemFontOfSize(CGFloat(13.0)) 395 | } 396 | } 397 | 398 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 399 | if isRepeatPickerCell(indexPath) || isWeekPickerCell(indexPath) { 400 | return TBRPPickerHeight 401 | } else if isMonthsCollectionCell(indexPath) { 402 | return TBRPMonthsCollectionHeight 403 | } else if isDaysCollectionCell(indexPath) { 404 | return TBRPDaysCollectionHeight 405 | } 406 | 407 | return 44.0 408 | } 409 | 410 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 411 | if indexPath.section == 0 { 412 | if isRepeatPickerCell(indexPath) { 413 | if indexPath == NSIndexPath(forRow: 1, inSection: 0) { 414 | let cell = TBRPPickerViewCell(style: .Default, reuseIdentifier: TBRPPickerViewCellID, pickerStyle: .Frequency, language: language) 415 | cell.frequency = frequency 416 | cell.delegate = self 417 | cell.selectionStyle = .None 418 | cell.accessoryType = .None 419 | return cell 420 | } else { 421 | let cell = TBRPPickerViewCell(style: .Default, reuseIdentifier: TBRPPickerViewCellID, pickerStyle: .Interval, language: language) 422 | cell.unit = unit() 423 | cell.interval = interval 424 | cell.delegate = self 425 | cell.selectionStyle = .None 426 | cell.accessoryType = .None 427 | return cell 428 | } 429 | } else { 430 | var cell = tableView.dequeueReusableCellWithIdentifier(TBRPCustomRepeatCellID) as? TBRPCustomRepeatCell 431 | if cell == nil { 432 | cell = TBRPCustomRepeatCell(style: .Value1, reuseIdentifier: TBRPCustomRepeatCellID) 433 | } 434 | cell?.selectionStyle = .Default 435 | cell!.accessoryType = .None 436 | 437 | if indexPath == frequencyTitleIndexpath { 438 | cell?.textLabel?.text = internationalControl?.localized("TBRPCustomRepeatController.textLabel.frequency", comment: "Frequency") 439 | cell?.detailTextLabel?.text = frequencies[(frequency?.rawValue)!] 440 | if hasRepeatPicker() && repeatPickerIndexPath == NSIndexPath(forRow: 1, inSection: 0) { 441 | cell?.detailTextLabel?.textColor = tintColor 442 | } else { 443 | cell?.detailTextLabel?.textColor = TBRPHelper.detailTextColor() 444 | } 445 | 446 | cell?.addSectionTopSeparator() 447 | } else if indexPath == intervalTitleIndexpath { 448 | cell?.textLabel?.text = internationalControl?.localized("TBRPCustomRepeatController.textLabel.interval", comment: "Every") 449 | cell?.detailTextLabel?.text = unitString() 450 | 451 | if hasRepeatPicker() && repeatPickerIndexPath == NSIndexPath(forRow: 2, inSection: 0) { 452 | cell?.updateBottomSeparatorWithLeftX(TBRPHelper.leadingMargin()) 453 | cell?.detailTextLabel?.textColor = tintColor 454 | } else { 455 | cell?.updateBottomSeparatorWithLeftX(0) 456 | cell?.detailTextLabel?.textColor = TBRPHelper.detailTextColor() 457 | } 458 | } 459 | 460 | return cell! 461 | } 462 | } else if indexPath.section == 1 { 463 | if frequency == .Weekly { 464 | var cell = tableView.dequeueReusableCellWithIdentifier(TBRPCustomRepeatCellID) as? TBRPCustomRepeatCell 465 | if cell == nil { 466 | cell = TBRPCustomRepeatCell(style: .Value1, reuseIdentifier: TBRPCustomRepeatCellID) 467 | } 468 | cell?.selectionStyle = .Default 469 | 470 | cell?.textLabel?.text = TBRPHelper.weekdays(language)[indexPath.row] 471 | cell?.detailTextLabel?.text = nil 472 | if selectedWeekdays.contains(indexPath.row) == true { 473 | cell?.accessoryType = .Checkmark 474 | } else { 475 | cell?.accessoryType = .None 476 | } 477 | 478 | if indexPath.row == 0 { 479 | cell?.addSectionTopSeparator() 480 | } else if indexPath.row == TBRPHelper.weekdays(language).count - 1 { 481 | cell?.updateBottomSeparatorWithLeftX(0) 482 | } 483 | 484 | return cell! 485 | } else if frequency == .Monthly { 486 | if indexPath.row == 2 { 487 | if byWeekNumber == true { 488 | let cell = TBRPPickerViewCell(style: .Default, reuseIdentifier: TBRPPickerViewCellID, pickerStyle: .Week, language: language) 489 | cell.delegate = self 490 | cell.pickedWeekNumber = pickedWeekNumber 491 | cell.pickedWeekday = pickedWeekday 492 | cell.selectionStyle = .None 493 | cell.accessoryType = .None 494 | return cell 495 | } else { 496 | let cell = TBRPCollectionViewCell(style: .Default, reuseIdentifier: TBRPCollectionViewCellID, mode: .Days, language: language) 497 | cell.selectionStyle = .None 498 | cell.selectedMonthdays = selectedMonthdays 499 | cell.delegate = self 500 | 501 | return cell 502 | } 503 | } else { 504 | var cell = tableView.dequeueReusableCellWithIdentifier(TBRPCustomRepeatCellID) as? TBRPCustomRepeatCell 505 | if cell == nil { 506 | cell = TBRPCustomRepeatCell(style: .Value1, reuseIdentifier: TBRPCustomRepeatCellID) 507 | } 508 | cell?.selectionStyle = .Default 509 | 510 | switch indexPath.row { 511 | case 0: 512 | cell?.textLabel?.text = internationalControl?.localized("TBRPCustomRepeatController.textLabel.date", comment: "Each") 513 | cell?.selectionStyle = .Default 514 | if byWeekNumber == true { 515 | cell?.accessoryType = .None 516 | } else { 517 | cell?.accessoryType = .Checkmark 518 | } 519 | cell?.addSectionTopSeparator() 520 | 521 | case 1: 522 | cell?.textLabel?.text = internationalControl?.localized("TBRPCustomRepeatController.weekCell.onThe", comment: "On the...") 523 | cell?.selectionStyle = .Default 524 | if byWeekNumber == true { 525 | cell?.accessoryType = .Checkmark 526 | } else { 527 | cell?.accessoryType = .None 528 | } 529 | 530 | cell?.addSectionBottomSeparator() 531 | 532 | default: 533 | cell?.textLabel?.text = nil 534 | } 535 | cell?.detailTextLabel?.text = nil 536 | 537 | return cell! 538 | } 539 | } else { 540 | let cell = TBRPCollectionViewCell(style: .Default, reuseIdentifier: TBRPCollectionViewCellID, mode: .Months, language: language) 541 | cell.selectionStyle = .None 542 | cell.selectedMonths = selectedMonths 543 | cell.delegate = self 544 | 545 | return cell 546 | } 547 | } else { 548 | if indexPath.row == 0 { 549 | let cell = TBRPSwitchCell(style: .Default, reuseIdentifier: TBRPSwitchCellID) 550 | 551 | if let _ = byWeekNumber { 552 | cell.weekSwitch?.setOn(byWeekNumber!, animated: true) 553 | } else { 554 | cell.weekSwitch?.setOn(false, animated: false) 555 | } 556 | cell.textLabel?.text = internationalControl?.localized("TBRPCustomRepeatController.weekCell.daysOfWeek", comment: "Days of Week") 557 | cell.selectionStyle = .None 558 | cell.accessoryType = .None 559 | cell.delegate = self 560 | 561 | cell.addSectionTopSeparator() 562 | if byWeekNumber == true { 563 | cell.updateBottomSeparatorWithLeftX(TBRPHelper.leadingMargin()) 564 | } else { 565 | cell.updateBottomSeparatorWithLeftX(0) 566 | } 567 | return cell 568 | } else { 569 | let cell = TBRPPickerViewCell(style: .Default, reuseIdentifier: TBRPPickerViewCellID, pickerStyle: .Week, language: language) 570 | cell.delegate = self 571 | cell.pickedWeekNumber = pickedWeekNumber 572 | cell.pickedWeekday = pickedWeekday 573 | cell.selectionStyle = .None 574 | cell.accessoryType = .None 575 | return cell 576 | } 577 | } 578 | } 579 | 580 | // MARK: - Table view delegate 581 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 582 | tableView.deselectRowAtIndexPath(indexPath, animated: true) 583 | if indexPath == repeatPickerIndexPath { 584 | return 585 | } 586 | 587 | let cell = tableView.cellForRowAtIndexPath(indexPath) 588 | if cell?.reuseIdentifier == TBRPCustomRepeatCellID { 589 | if indexPath.section == 0 { 590 | tableView.beginUpdates() 591 | 592 | if hasRepeatPicker() { 593 | let repeatPickerIndexPathTemp = repeatPickerIndexPath 594 | closeRepeatPicker() 595 | 596 | if indexPath.row == (repeatPickerIndexPathTemp!.row) - 1 { 597 | 598 | } else { 599 | if indexPath == frequencyTitleIndexpath { 600 | repeatPickerIndexPath = NSIndexPath(forRow: indexPath.row + 1, inSection: indexPath.section) 601 | } else { 602 | repeatPickerIndexPath = indexPath 603 | } 604 | 605 | tableView.insertRowsAtIndexPaths([repeatPickerIndexPath!], withRowAnimation: .Fade) 606 | } 607 | } else { 608 | repeatPickerIndexPath = NSIndexPath(forRow: indexPath.row + 1, inSection: indexPath.section) 609 | tableView.insertRowsAtIndexPaths([repeatPickerIndexPath!], withRowAnimation: .Fade) 610 | } 611 | 612 | tableView.endUpdates() 613 | 614 | updateIntervalCellBottomSeparator() 615 | updateDetailTextColor() 616 | } else if indexPath.section == 1 { 617 | if frequency == .Weekly { 618 | if hasRepeatPicker() { 619 | tableView.beginUpdates() 620 | closeRepeatPicker() 621 | tableView.endUpdates() 622 | 623 | updateIntervalCellBottomSeparator() 624 | } 625 | 626 | let cell = tableView.cellForRowAtIndexPath(indexPath) 627 | let day = indexPath.row 628 | 629 | if selectedWeekdays.count == 1 && selectedWeekdays.contains(day) == true { 630 | return 631 | } 632 | 633 | if selectedWeekdays.contains(day) == true { 634 | cell?.accessoryType = .None 635 | selectedWeekdays.removeObject(day) 636 | } else { 637 | cell?.accessoryType = .Checkmark 638 | selectedWeekdays.append(day) 639 | } 640 | 641 | updateFooterTitle() 642 | } else if frequency == .Monthly { 643 | let dateCellIndexPath = NSIndexPath(forRow: 0, inSection: 1) 644 | let weekCellIndexPath = NSIndexPath(forRow: 1, inSection: 1) 645 | let dateCell = tableView.cellForRowAtIndexPath(dateCellIndexPath) 646 | let weekCell = tableView.cellForRowAtIndexPath(weekCellIndexPath) 647 | 648 | if indexPath == weekCellIndexPath && byWeekNumber == false { 649 | byWeekNumber = true 650 | weekCell?.accessoryType = .Checkmark 651 | dateCell?.accessoryType = .None 652 | } else if indexPath == dateCellIndexPath && byWeekNumber == true { 653 | byWeekNumber = false 654 | dateCell?.accessoryType = .Checkmark 655 | weekCell?.accessoryType = .None 656 | } 657 | 658 | updateFooterTitle() 659 | } 660 | } 661 | } 662 | } 663 | 664 | // MARK: - TBRPPickerCell delegate 665 | func pickerDidPick(pickerView: UIPickerView, pickStyle: TBRPPickerStyle, didSelectRow row: Int, inComponent component: Int) { 666 | if pickStyle == .Frequency { 667 | frequency = TBRPFrequency(rawValue: row) 668 | } else if pickStyle == .Interval { 669 | if component == 0 { 670 | interval = row + 1 671 | } 672 | } else if pickStyle == .Week { 673 | if hasRepeatPicker() { 674 | tableView.beginUpdates() 675 | closeRepeatPicker() 676 | tableView.endUpdates() 677 | 678 | updateIntervalCellBottomSeparator() 679 | } 680 | 681 | if component == 0 { 682 | pickedWeekNumber = TBRPWeekPickerNumber(rawValue: row)! 683 | } else if component == 1 { 684 | pickedWeekday = TBRPWeekPickerDay(rawValue: row)! 685 | } 686 | } 687 | 688 | updateFooterTitle() 689 | } 690 | 691 | // MARK: - TBRPSwitchCell delegate 692 | func didSwitch(sender: AnyObject) { 693 | if let weekSwitch = sender as? UISwitch { 694 | byWeekNumber = weekSwitch.on 695 | 696 | updateYearlyWeekCellBottomSeparator() 697 | updateFooterTitle() 698 | } 699 | } 700 | 701 | 702 | // MARK: - TBRPCollectionViewCell delegate 703 | func selectedMonthdaysDidChanged(days: [Int]) { 704 | if hasRepeatPicker() { 705 | tableView.beginUpdates() 706 | closeRepeatPicker() 707 | tableView.endUpdates() 708 | 709 | updateIntervalCellBottomSeparator() 710 | } 711 | 712 | selectedMonthdays = days 713 | 714 | updateFooterTitle() 715 | } 716 | 717 | func selectedMonthsDidChanged(months: [Int]) { 718 | if hasRepeatPicker() { 719 | tableView.beginUpdates() 720 | closeRepeatPicker() 721 | tableView.endUpdates() 722 | 723 | updateIntervalCellBottomSeparator() 724 | } 725 | 726 | selectedMonths = months 727 | 728 | updateFooterTitle() 729 | } 730 | } 731 | --------------------------------------------------------------------------------