├── Assets ├── about.png ├── socialPreview.png └── ALRadioButtons.sketch ├── ExampleApp ├── iOS App │ ├── Sources │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── ipad-20x20.png │ │ │ │ ├── ipad-29x29.png │ │ │ │ ├── ipad-40x40.png │ │ │ │ ├── ipad-76x76.png │ │ │ │ ├── ipad-20x20@2x.png │ │ │ │ ├── ipad-29x29@2x.png │ │ │ │ ├── ipad-40x40@2x.png │ │ │ │ ├── ipad-76x76@2x.png │ │ │ │ ├── iphone-20x20@2x.png │ │ │ │ ├── iphone-20x20@3x.png │ │ │ │ ├── iphone-29x29@2x.png │ │ │ │ ├── iphone-29x29@3x.png │ │ │ │ ├── iphone-40x40@2x.png │ │ │ │ ├── iphone-40x40@3x.png │ │ │ │ ├── iphone-60x60@2x.png │ │ │ │ ├── iphone-60x60@3x.png │ │ │ │ ├── ipad-83.5x83.5@2x.png │ │ │ │ ├── ios-marketing-1024x1024.png │ │ │ │ └── Contents.json │ │ ├── Views │ │ │ ├── ALHeaderView.swift │ │ │ └── ALScrollView.swift │ │ └── Info.plist │ ├── AppDelegate.swift │ └── Controllers │ │ ├── Launch │ │ └── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── RadioGroupViewController.swift │ │ └── MainViewController.swift └── ALRadioButtons Example.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── alxrguz.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── xcuserdata │ └── alxrguz.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── xcshareddata │ └── xcschemes │ │ └── iOS App.xcscheme │ └── project.pbxproj ├── Package.swift ├── .gitignore ├── ALRadioButtons.podspec ├── Sources ├── SupportingFiles │ ├── Info.plist │ └── RGRadioGroup.h └── ALRadioButtons │ ├── Model │ ├── RadioGroupStyle.swift │ └── RadioItem.swift │ └── Views │ ├── ALRadioIndicator.swift │ ├── ALRadioGroupItem.swift │ └── ALRadioGroup.swift ├── LICENSE └── README.md /Assets/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/Assets/about.png -------------------------------------------------------------------------------- /Assets/socialPreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/Assets/socialPreview.png -------------------------------------------------------------------------------- /Assets/ALRadioButtons.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/Assets/ALRadioButtons.sketch -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ios-marketing-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ios-marketing-1024x1024.png -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/xcuserdata/alxrguz.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/xcuserdata/alxrguz.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALRadioButtons/HEAD/ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/xcuserdata/alxrguz.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SnapKit", 6 | "repositoryURL": "https://github.com/SnapKit/SnapKit", 7 | "state": { 8 | "branch": null, 9 | "revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6", 10 | "version": "5.0.1" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ALRadioButtons", 8 | platforms: [ 9 | .iOS(.v10) 10 | ], 11 | products: [ 12 | .library(name: "ALRadioButtons",targets: ["ALRadioButtons"]), 13 | ], 14 | targets: [ 15 | .target(name: "ALRadioButtons") 16 | ] 17 | ) 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # osX files 2 | .DS_Store 3 | .Trashes 4 | Icon 5 | Icon? 6 | 7 | ## Xcode Patch 8 | *.xcworkspace 9 | *.xcuserdata 10 | *.xcodeproj/* 11 | !*.xcodeproj/project.pbxproj 12 | !*.xcodeproj/xcshareddata/ 13 | !*.xcworkspace/contents.xcworkspacedata 14 | /*.gcno 15 | 16 | ### Xcode Patch ### 17 | **/xcshareddata/WorkspaceSettings.xcsettings 18 | 19 | # Swift Package Manager 20 | .swiftpm 21 | /.build 22 | 23 | 24 | ## Build generated 25 | build/ 26 | DerivedData/ 27 | 28 | ## Various settings 29 | *.pbxuser 30 | !default.pbxuser 31 | *.mode1v3 32 | !default.mode1v3 33 | *.mode2v3 34 | !default.mode2v3 35 | *.perspectivev3 36 | !default.perspectivev3 37 | xcuserdata/ -------------------------------------------------------------------------------- /ExampleApp/iOS App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // 4 | // Created by Alexandr Guzenko on 17.08.2020. 5 | // Copyright © 2020. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | var window: UIWindow? 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | window = UIWindow() 15 | window?.rootViewController = UINavigationController(rootViewController: MainViewController()) 16 | window?.makeKeyAndVisible() 17 | return true 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.xcworkspace/xcuserdata/alxrguz.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | ShowSharedSchemesAutomaticallyEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ALRadioButtons.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "ALRadioButtons" 3 | spec.version = "1.2.2" 4 | spec.summary = "RadioButtons for iOS. Inherited from UIControl, support 2 native styles, fully customizable." 5 | 6 | spec.homepage = "https://github.com/alxrguz/ALRadioButtons" 7 | spec.source = { :git => "https://github.com/alxrguz/ALRadioButtons.git", :tag => "#{spec.version}" } 8 | spec.license = { :type => "MIT", :file => "LICENSE" } 9 | 10 | spec.author = { "Alexandr Guzenko" => "alxrguz@icloud.com" } 11 | 12 | spec.platform = :ios 13 | spec.ios.framework = 'UIKit' 14 | spec.swift_version = ['4.2', '5.0'] 15 | spec.ios.deployment_target = "10.0" 16 | 17 | spec.source_files = "Sources/ALRadioButtons/**/*.swift" 18 | 19 | end -------------------------------------------------------------------------------- /Sources/SupportingFiles/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexandr Guzenko 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 | -------------------------------------------------------------------------------- /Sources/ALRadioButtons/Model/RadioGroupStyle.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2020 Alexandr Guzenko (alxrguz@icloud.com) 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 | import Foundation 24 | 25 | /** 26 | Стиль для `ALRadioButton` 27 | */ 28 | public enum ALRadioButtonStyle { 29 | /// The style is similar to UITableView insertGrouped style 30 | case grouped 31 | 32 | /// The style is similar to UITableView plain style 33 | case standard 34 | } 35 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Views/ALHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALHeaderView.swift 3 | // iOS App 4 | // 5 | // Created by Alexandr Guzenko on 30.07.2021. 6 | // Copyright © 2021 alxrguz. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ALHeaderView: UIView { 12 | // MARK: - UI Elements 13 | public lazy var titleLabel = UILabel() 14 | public lazy var subtitleLabel = UILabel() 15 | 16 | // MARK: - Life cycle 17 | init() { 18 | super.init(frame: .zero) 19 | setupView() 20 | setupConstraints() 21 | } 22 | 23 | required init?(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | } 27 | 28 | // MARK: - Layout Setup 29 | private extension ALHeaderView { 30 | func setupColors() { 31 | titleLabel.textColor = .label 32 | subtitleLabel.textColor = .secondaryLabel 33 | } 34 | 35 | func setupView() { 36 | setupColors() 37 | 38 | titleLabel.font = .systemFont(ofSize: 20, weight: .bold) 39 | subtitleLabel.font = .systemFont(ofSize: 15, weight: .regular) 40 | } 41 | 42 | func setupConstraints() { 43 | addSubview(titleLabel) 44 | addSubview(subtitleLabel) 45 | 46 | titleLabel.snp.makeConstraints { 47 | $0.top.leading.trailing.equalToSuperview() 48 | } 49 | 50 | subtitleLabel.snp.makeConstraints { 51 | $0.top.equalTo(titleLabel.snp.bottom).offset(2) 52 | $0.bottom.leading.trailing.equalToSuperview() 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Sources/SupportingFiles/RGRadioGroup.h: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2020 Alexandr Guzenko (alxrguz@icloud.com) 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 | #import 24 | 25 | //! Project version number for RGRadioGroup. 26 | FOUNDATION_EXPORT double RGRadioGroupVersionNumber; 27 | 28 | //! Project version string for RGRadioGroup. 29 | FOUNDATION_EXPORT const unsigned char RGRadioGroupVersionString[]; 30 | 31 | // In this header, you should import all the public headers of your framework using statements like #import 32 | 33 | 34 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ALRadioButtons 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Views/ALScrollView.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import UIKit 4 | import SnapKit 5 | 6 | public class ALScrollView: UIScrollView { 7 | // MARK: UI Elements 8 | public lazy var contentView = UIView() 9 | public lazy var backgroundView = UIView() 10 | 11 | // MARK: Public Properties 12 | var orientation: NSLayoutConstraint.Axis = .vertical { didSet { remakeConstraints() } } 13 | 14 | // MARK: Lifecycle 15 | public init(orientation: NSLayoutConstraint.Axis = .vertical) { 16 | super.init(frame: .zero) 17 | delaysContentTouches = false 18 | self.orientation = orientation 19 | 20 | addSubview(backgroundView) 21 | addSubview(contentView) 22 | } 23 | 24 | public required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | public override func touchesShouldCancel(in view: UIView) -> Bool { 29 | if view is UIControl && (view is UISwitch) == false { return true } 30 | return super.touchesShouldCancel(in: view) 31 | } 32 | 33 | public override func didMoveToSuperview() { 34 | super.didMoveToSuperview() 35 | remakeConstraints() 36 | } 37 | 38 | public func remakeConstraints() { 39 | guard let superview = self.superview else { return} 40 | backgroundView.snp.remakeConstraints { 41 | $0.edges.equalTo(superview) 42 | } 43 | 44 | contentView.snp.remakeConstraints { 45 | $0.edges.equalToSuperview() 46 | if orientation == .vertical { 47 | $0.width.equalToSuperview() 48 | } else { 49 | $0.height.equalToSuperview() 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/xcuserdata/alxrguz.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SnapKitPlayground (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 3 13 | 14 | SnapKitPlayground (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 4 20 | 21 | SnapKitPlayground (Playground) 3.xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 4 27 | 28 | SnapKitPlayground (Playground) 4.xcscheme 29 | 30 | isShown 31 | 32 | orderHint 33 | 5 34 | 35 | SnapKitPlayground (Playground) 5.xcscheme 36 | 37 | isShown 38 | 39 | orderHint 40 | 6 41 | 42 | SnapKitPlayground (Playground).xcscheme 43 | 44 | isShown 45 | 46 | orderHint 47 | 1 48 | 49 | iOS App.xcscheme_^#shared#^_ 50 | 51 | orderHint 52 | 0 53 | 54 | 55 | SuppressBuildableAutocreation 56 | 57 | BB3959F224EA71EE00B965F5 58 | 59 | primary 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/Launch/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 | -------------------------------------------------------------------------------- /Sources/ALRadioButtons/Model/RadioItem.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2020 Alexandr Guzenko (alxrguz@icloud.com) 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 | import Foundation 24 | 25 | 26 | /** 27 | Text content for `ALRadioButton` 28 | */ 29 | public struct ALRadioItem { 30 | /// The main text that appears next to the indicator 31 | public let title: String 32 | 33 | /// Optional text under the heading 34 | public let subtitle: String? 35 | 36 | /// Optional text to the right of the title 37 | public let detail: String? 38 | 39 | public init(title: String, subtitle: String? = nil, detail: String? = nil) { 40 | self.title = title 41 | self.subtitle = subtitle 42 | self.detail = detail 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iphone-20x20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "iphone-20x20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "iphone-29x29@2x.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "iphone-29x29@3x.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "iphone-40x40@2x.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "iphone-40x40@3x.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "iphone-60x60@2x.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "iphone-60x60@3x.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "ipad-20x20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "ipad-20x20@2x.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "ipad-29x29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "ipad-29x29@2x.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "ipad-40x40.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "ipad-40x40@2x.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "ipad-76x76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "ipad-76x76@2x.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "ipad-83.5x83.5@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "ios-marketing-1024x1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/xcshareddata/xcschemes/iOS App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ALRadioButtons 2 | 3 | 4 | 5 | 6 | 7 |   8 | 9 | ## Navigation 10 | 11 | - [Requirements](#requirements) 12 | - [Installation](#installation) 13 | - [Swift Package Manager](#Swift-Package-Manager) 14 | - [CocoaPods](#CocoaPods) 15 | - [Manually](#Manually) 16 | - [Usage](#usage) 17 | - [Quick Start](#Quick-Start) 18 | - [Customization](#Customization) 19 | - [Colors](#colors) 20 | - [Fonts](#Fonts) 21 | - [Layout](#Layout) 22 | - [License](https://github.com/SnapKit/SnapKit#license) 23 | 24 | ## 25 | 26 | ## Requirements 27 | 28 | - iOS 10.0 + 29 | - Swift 4.2 + 30 | 31 | 32 | 33 | ## Installation 34 | 35 | #### Swift Package Manager 36 | 37 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 38 | 39 | To integrate **ALRadioButtons** click `File -> Swift Package -> Add Package Dependency` and insert: 40 | 41 | ```ogdl 42 | https://github.com/alxrguz/ALRadioButtons 43 | ``` 44 | 45 | #### CocoaPods 46 | 47 | **ALRadioButtons** is available through [CocoaPods](https://cocoapods.org/pods/ALRadioButtons). To install it, simply add the following line to your Podfile: 48 | 49 | ```ruby 50 | pod 'ALRadioButtons' 51 | ``` 52 | 53 | #### Manually 54 | 55 | If you prefer not to use either of the aforementioned dependency managers, you can integrate ALRadioButtons into your project manually. Put `Source/ALRadioButtons` folder in your Xcode project. 56 | 57 | 58 | 59 | ## Usage 60 | 61 | #### Quick Start 62 | 63 | ```swift 64 | import ALRadioButtons 65 | 66 | class MyViewController: UIViewController { 67 | 68 | lazy var radioGroup = ALRadioGroup(items: [ 69 | .init(title: "title1", subtitle: "subtitle1"), 70 | .init(title: "title2", subtitle: "subtitle2", detail: "Detail"), 71 | .init(title: "title3"), 72 | ], style: .grouped) 73 | 74 | override func viewDidLoad() { 75 | super.viewDidLoad() 76 | 77 | self.view.addSubview(radioGroup) 78 | // ... Setup layout 79 | 80 | radioGroup.selectedIndex = 0 81 | radioGroup.addTarget(self, action: #selector(radioGroupSelected(_:)), for: .valueChanged) 82 | 83 | // If the checkbox selection can be canceled, then set this property to true 84 | radioGroup.allowDeselection = true 85 | } 86 | 87 | @objc private func radioGroupSelected(_ sender: ALRadioGroup) { 88 | print(sender.selectedIndex) 89 | } 90 | } 91 | ``` 92 | 93 | 94 | 95 | #### Customization 96 | 97 | #### Colors 98 | 99 | You can customize the buttons, headers and indicators colors depending on their state. 100 | 101 | ```swift 102 | radioGroup.selectedTitleColor = .systemBlue 103 | radioGroup.unselectedTitleColor = .black 104 | radioGroup.selectedDetailColor = .systemBlue 105 | radioGroup.unselectedDetailColor = .black 106 | radioGroup.selectedIndicatorColor = .systemBlue 107 | radioGroup.unselectedIndicatorColor = .systemBlue 108 | radioGroup.subtitleColor = .lightGray 109 | radioGroup.buttonBackgroundColor = .white 110 | radioGroup.separatorColor = .lightGray 111 | ``` 112 | 113 | 114 | 115 | #### Font 116 | 117 | ```swift 118 | radioGroup.titleFont = .systemFont(ofSize: 16, weight: .medium) 119 | radioGroup.detailFont = .systemFont(ofSize: 16, weight: .regular) 120 | radioGroup.subtitleFont = .systemFont(ofSize: 13, weight: .regular) 121 | ``` 122 | 123 | 124 | 125 | #### Layout 126 | 127 | ```swift 128 | radioGroup.cornerRadius = 14 // Button corner radius, actual for .grouped style 129 | radioGroup.buttonHeight = 50 130 | radioGroup.subtitleTopOffset = 8 // Subtitle offset from title bottom anchor 131 | radioGroup.separatorTopOffset = 8 // Separator offset from title or subtitle (if added) bottom anchor 132 | radioGroup.indicatorRingWidth = 2 // Outer ring width of indicator 133 | radioGroup.indicatorRingSize = 22 // Indicator outer ring size 134 | radioGroup.indicatorCircleInset = 5 // Indentation of the circle from the outer ring 135 | ``` 136 | 137 | 138 | 139 | ## License 140 | 141 | **ALRadioButtons** is under MIT license. See the [LICENSE](https://github.com/alxrguz/ALRadioButtons/blob/master/LICENSE) file for more info. 142 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/RadioGroupViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleExampleViewController.swift 3 | // iOS App 4 | // 5 | // Created by Alexandr Guzenko on 25.08.2021. 6 | // Copyright © 2021 alxrguz. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import ALRadioButtons 12 | 13 | class RadioGroupViewController: UIViewController { 14 | 15 | // MARK: - UIElements 16 | 17 | private lazy var contentView: UIView = { 18 | switch buttomType { 19 | case .simple(let style): 20 | return createSimpleView(style: style) 21 | case .subtitle(let style): 22 | return createSubtitleView(style: style) 23 | case .all(let style): 24 | return createSubtitleAndDetailView(style: style) 25 | } 26 | }() 27 | 28 | // MARK: - Private properties 29 | 30 | private var buttomType: MainViewController.RadioButtonType 31 | 32 | // MARK: - Life cycle 33 | 34 | init(type: MainViewController.RadioButtonType) { 35 | self.buttomType = type 36 | super.init(nibName: nil, bundle: nil) 37 | } 38 | 39 | required init?(coder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | 44 | // MARK: - UIViewController 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | setupView() 49 | setupConstraints() 50 | } 51 | } 52 | 53 | // MARK: - Setup Layout 54 | 55 | private extension RadioGroupViewController { 56 | func setupColors() { 57 | view.backgroundColor = .systemGroupedBackground 58 | } 59 | 60 | func createSimpleView(style: ALRadioButtonStyle) -> UIView { 61 | let contentView = UIView() 62 | let group1HeaderView = ALHeaderView() 63 | group1HeaderView.subtitleLabel.text = "Vertical" 64 | let radioGroup1 = ALRadioGroup( 65 | items: [.init(title: "Variant 1"), 66 | .init(title: "Variant 2"), 67 | .init(title: "Variant 3")], 68 | style: style 69 | ) 70 | radioGroup1.axis = .vertical 71 | 72 | let group2HeaderView = ALHeaderView() 73 | group2HeaderView.subtitleLabel.text = "Horizontal" 74 | let radioGroup2 = ALRadioGroup( 75 | items: [.init(title: "Variant 1"), 76 | .init(title: "Variant 2")], 77 | style: style 78 | ) 79 | radioGroup2.separatorColor = .clear 80 | radioGroup2.axis = .horizontal 81 | 82 | contentView.addSubview(group1HeaderView) 83 | contentView.addSubview(radioGroup1) 84 | contentView.addSubview(group2HeaderView) 85 | contentView.addSubview(radioGroup2) 86 | 87 | group1HeaderView.snp.makeConstraints { 88 | $0.top.leading.trailing.equalToSuperview() 89 | } 90 | 91 | radioGroup1.snp.makeConstraints { 92 | $0.top.equalTo(group1HeaderView.snp.bottom).offset(10) 93 | $0.leading.trailing.equalToSuperview() 94 | } 95 | 96 | group2HeaderView.snp.makeConstraints { 97 | $0.top.equalTo(radioGroup1.snp.bottom).offset(30) 98 | $0.leading.trailing.equalToSuperview() 99 | } 100 | 101 | radioGroup2.snp.makeConstraints { 102 | $0.top.equalTo(group2HeaderView.snp.bottom).offset(10) 103 | $0.leading.trailing.equalToSuperview() 104 | $0.bottom.equalToSuperview() 105 | } 106 | 107 | return contentView 108 | } 109 | 110 | func createSubtitleView(style: ALRadioButtonStyle) -> UIView { 111 | return ALRadioGroup( 112 | items: [.init(title: "Variant 1", subtitle: "Description to the variant"), 113 | .init(title: "Variant 2", subtitle: "Description to the variant"), 114 | .init(title: "Variant 3", subtitle: "Description to the variant")], 115 | style: style 116 | ) 117 | } 118 | 119 | func createSubtitleAndDetailView(style: ALRadioButtonStyle) -> UIView { 120 | return ALRadioGroup( 121 | items: [.init(title: "Variant 1", subtitle: "Description to the variant", detail: "Detail"), 122 | .init(title: "Variant 2", subtitle: "Description to the variant", detail: "Detail"), 123 | .init(title: "Variant 3", subtitle: "Description to the variant", detail: "Detail")], 124 | style: style 125 | ) 126 | } 127 | 128 | func setupView() { 129 | setupColors() 130 | 131 | navigationItem.title = buttomType.title 132 | } 133 | 134 | func setupConstraints() { 135 | view.addSubview(contentView) 136 | 137 | contentView.snp.makeConstraints { 138 | $0.top.equalTo(view.safeAreaLayoutGuide).offset(20) 139 | $0.leading.trailing.equalToSuperview().inset(16) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // 4 | // Created by Alexandr Guzenko on 17.08.2020. 5 | // Copyright © 2020. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import ALRadioButtons 10 | 11 | class MainViewController: UIViewController { 12 | 13 | // MARK: - UIElements 14 | 15 | private lazy var tableView = UITableView(frame: .zero, style: .insetGrouped) 16 | 17 | // MARK: - Private Properties 18 | 19 | let data: [SectionData] = [ 20 | .init(title: "Simple", footer: "Regular radio buttons, no additional information", content: [.simple(style: .standard), .simple(style: .grouped)]), 21 | .init(title: "Subtitle", footer: "This example contains a description for each radio button", content: [.subtitle(style: .standard), .subtitle(style: .grouped)]), 22 | .init(title: "Subtitle & Detail", footer: "In addition to the description, detailed information is displayed on the right (like UITableViewCell)", content: [.all(style: .standard), .all(style: .grouped)]) 23 | ] 24 | 25 | // MARK: - UIViewController 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | setupView() 30 | setupConstraints() 31 | } 32 | } 33 | 34 | // MARK: - UITableViewDelegate 35 | 36 | extension MainViewController: UITableViewDataSource { 37 | func numberOfSections(in tableView: UITableView) -> Int { 38 | data.count 39 | } 40 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 41 | data[section].content.count 42 | } 43 | 44 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 45 | let cell = UITableViewCell() 46 | let data = self.data[indexPath.section].content[indexPath.row].styleTitle 47 | cell.textLabel?.text = data 48 | cell.accessoryType = .disclosureIndicator 49 | return cell 50 | } 51 | 52 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 53 | data[section].title 54 | } 55 | 56 | func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 57 | data[section].footer 58 | } 59 | } 60 | 61 | // MARK: - UITableViewDelegate 62 | 63 | extension MainViewController: UITableViewDelegate { 64 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 65 | tableView.deselectRow(at: indexPath, animated: true) 66 | let data = self.data[indexPath.section].content[indexPath.row] 67 | let vc = RadioGroupViewController(type: data) 68 | self.navigationController?.pushViewController(vc, animated: true) 69 | } 70 | } 71 | 72 | // MARK: - Setup Layout 73 | 74 | private extension MainViewController { 75 | func setupColors() { 76 | view.backgroundColor = .systemGroupedBackground 77 | } 78 | 79 | func setupView() { 80 | setupColors() 81 | 82 | navigationItem.title = "ALRadioButtons" 83 | 84 | tableView.dataSource = self 85 | tableView.delegate = self 86 | 87 | } 88 | 89 | func setupConstraints() { 90 | view.addSubview(tableView) 91 | 92 | tableView.snp.makeConstraints { 93 | $0.edges.equalToSuperview() 94 | } 95 | } 96 | } 97 | 98 | // MARK: - Data Structures 99 | 100 | extension MainViewController { 101 | struct SectionData { 102 | var title: String? 103 | var footer: String? 104 | var content: [RadioButtonType] 105 | } 106 | 107 | enum RadioButtonType { 108 | case simple(style: ALRadioButtonStyle) 109 | case subtitle(style: ALRadioButtonStyle) 110 | case all(style: ALRadioButtonStyle) 111 | 112 | var styleTitle: String { 113 | switch self { 114 | case .simple(let style), .all(let style), .subtitle(let style): 115 | switch style { 116 | case .grouped: 117 | return "Grouped" 118 | case .standard: 119 | return "Standart" 120 | } 121 | } 122 | } 123 | 124 | var title: String { 125 | switch self { 126 | case .simple(let style): 127 | switch style { 128 | case .grouped: 129 | return "Simple (Grouped)" 130 | case .standard: 131 | return "Simple (Standart)" 132 | } 133 | case .all(let style): 134 | switch style { 135 | case .grouped: 136 | return "Subtitle & Detail (Grouped)" 137 | case .standard: 138 | return "Subtitle & Detail (Standart)" 139 | } 140 | case .subtitle(let style): 141 | switch style { 142 | case .grouped: 143 | return "Subtitle (Grouped)" 144 | case .standard: 145 | return "Subtitle (Standart)" 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Sources/ALRadioButtons/Views/ALRadioIndicator.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2020 Alexandr Guzenko (alxrguz@icloud.com) 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 | import UIKit 24 | 25 | public final class ALRadioIndicator: UIView { 26 | // MARK: - UI Elements 27 | private lazy var selectedView = UIView() 28 | 29 | // MARK: - Open Proporties 30 | public var isSelected: Bool = false { 31 | didSet { 32 | guard oldValue != isSelected else { return } 33 | if isSelected == true { 34 | selectedView.transform = .init(scaleX: 0.95, y: 0.95) 35 | 36 | UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.3) { 37 | self.setupColors() 38 | } 39 | 40 | UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.3) { 41 | self.selectedView.transform = .identity 42 | } 43 | } else { 44 | setupColors() 45 | } 46 | } 47 | } 48 | 49 | public var selectedColor: UIColor = .systemBlue { 50 | didSet { 51 | setupColors() 52 | } 53 | } 54 | 55 | public var unselectedColor: UIColor = .lightGray { 56 | didSet { 57 | setupColors() 58 | } 59 | } 60 | 61 | public var ringWidth: CGFloat = 2 { 62 | didSet { 63 | layer.borderWidth = ringWidth 64 | } 65 | } 66 | 67 | public var size: CGFloat = 22 { 68 | didSet { 69 | outRingHeightConstraint?.constant = size 70 | outRingWidthConstraint?.constant = size 71 | } 72 | } 73 | 74 | public var selectedViewInset: CGFloat = 5 { 75 | didSet { 76 | let selectedViewSize = size - selectedViewInset * 2 77 | selectedViewWidthConstraint?.constant = selectedViewSize 78 | selectedViewHeightConstraint?.constant = selectedViewSize 79 | } 80 | } 81 | 82 | // MARK: - Private Proporties 83 | var outRingHeightConstraint: NSLayoutConstraint? 84 | var outRingWidthConstraint: NSLayoutConstraint? 85 | var selectedViewWidthConstraint: NSLayoutConstraint? 86 | var selectedViewHeightConstraint: NSLayoutConstraint? 87 | 88 | // MARK: - Life cycle 89 | init() { 90 | super.init(frame: .zero) 91 | setupView() 92 | setupConstraints() 93 | } 94 | 95 | required init?(coder: NSCoder) { 96 | fatalError("init(coder:) has not been implemented") 97 | } 98 | 99 | public override func layoutSubviews() { 100 | super.layoutSubviews() 101 | setupFrame() 102 | } 103 | } 104 | 105 | 106 | // MARK: - Layout Setup 107 | private extension ALRadioIndicator { 108 | func setupColors() { 109 | if isSelected { 110 | layer.borderColor = selectedColor.cgColor 111 | selectedView.backgroundColor = selectedColor 112 | } else { 113 | layer.borderColor = unselectedColor.cgColor 114 | selectedView.backgroundColor = .clear 115 | } 116 | } 117 | 118 | func setupView() { 119 | setupColors() 120 | layer.borderWidth = ringWidth 121 | } 122 | 123 | func setupFrame() { 124 | selectedView.layer.cornerRadius = selectedView.frame.height / 2 125 | self.layer.cornerRadius = self.frame.height / 2 126 | } 127 | 128 | func setupConstraints() { 129 | self.translatesAutoresizingMaskIntoConstraints = false 130 | outRingHeightConstraint = self.heightAnchor.constraint(equalToConstant: size) 131 | outRingWidthConstraint = self.widthAnchor.constraint(equalToConstant: size) 132 | NSLayoutConstraint.activate([outRingHeightConstraint!, outRingWidthConstraint!]) 133 | 134 | addSubview(selectedView) 135 | selectedView.translatesAutoresizingMaskIntoConstraints = false 136 | let selectedViewSize = size - selectedViewInset * 2 137 | selectedViewWidthConstraint = selectedView.widthAnchor.constraint(equalToConstant: selectedViewSize) 138 | selectedViewHeightConstraint = selectedView.heightAnchor.constraint(equalToConstant: selectedViewSize) 139 | NSLayoutConstraint.activate([ 140 | selectedViewWidthConstraint!, 141 | selectedViewHeightConstraint!, 142 | selectedView.centerYAnchor.constraint(equalTo: self.centerYAnchor), 143 | selectedView.centerXAnchor.constraint(equalTo: self.centerXAnchor) 144 | ]) 145 | } 146 | } 147 | 148 | -------------------------------------------------------------------------------- /Sources/ALRadioButtons/Views/ALRadioGroupItem.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2020 Alexandr Guzenko (alxrguz@icloud.com) 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 | import UIKit 24 | 25 | public final class ALRadioButton: UIView { 26 | // MARK: - UI Elements 27 | 28 | /// Radio Item title label 29 | public lazy var titleLabel = UILabel() 30 | 31 | /// Radio Item detail label 32 | public lazy var detailLabel = UILabel() 33 | 34 | /// Radio Item subtitle label 35 | public lazy var subtitleLabel = UILabel() 36 | 37 | /// Round indicator 38 | public lazy var radioIndicator = ALRadioIndicator() 39 | 40 | /// View which is located `titleLabel` and `radioButton` 41 | public lazy var itemView = UIView() 42 | 43 | /// Horizontal stack for `titleLabel` and `radioButton` 44 | private lazy var titleStackView = UIStackView() 45 | 46 | /// Separator like `tableView` for `standard` style 47 | private lazy var separatorView = UIView() 48 | 49 | // MARK: - Open Proporties 50 | 51 | /** 52 | button state 53 | */ 54 | public var isSelected: Bool = false { 55 | didSet { 56 | radioIndicator.isSelected = isSelected 57 | setupColors() 58 | } 59 | } 60 | 61 | /** 62 | `titleLabel` color when button selected 63 | */ 64 | public var selectedTitleColor: UIColor = .systemBlue { 65 | didSet { 66 | setupColors() 67 | } 68 | } 69 | 70 | /** 71 | `titleLabel` color when button unselected 72 | */ 73 | public var unselectedTitleColor: UIColor = .black { 74 | didSet { 75 | setupColors() 76 | } 77 | } 78 | 79 | /** 80 | `detailLabel` color when button selected 81 | */ 82 | public var selectedDetailColor: UIColor = .systemBlue { 83 | didSet { 84 | setupColors() 85 | } 86 | } 87 | 88 | /** 89 | `detailLabel` color when button unselected 90 | */ 91 | public var unselectedDetailColor: UIColor = .lightGray { 92 | didSet { 93 | setupColors() 94 | } 95 | } 96 | 97 | /** 98 | `radioIndicator` color when button selected 99 | */ 100 | public var selectedIndicatorColor: UIColor = .systemBlue { 101 | didSet { 102 | radioIndicator.selectedColor = selectedIndicatorColor 103 | } 104 | } 105 | 106 | /** 107 | `radioIndicator` color when button unselected 108 | */ 109 | public var unselectedIndicatorColor: UIColor = .systemBlue { 110 | didSet { 111 | radioIndicator.unselectedColor = unselectedIndicatorColor 112 | } 113 | } 114 | 115 | /** 116 | Separator color 117 | 118 | - Note: Actual for `ALRadioButtonStyle.standard` style 119 | */ 120 | public var separatorColor: UIColor = .lightGray { 121 | didSet { 122 | separatorView.backgroundColor = separatorColor 123 | } 124 | } 125 | 126 | /** 127 | View height at which indicator and title are located, subtitle is not included here 128 | */ 129 | public var buttonHeight: CGFloat = 50 { 130 | didSet { 131 | itemHeightConstraint?.constant = buttonHeight 132 | } 133 | } 134 | 135 | /** 136 | `subtitleLabel` offset from title bottom anchor 137 | */ 138 | public var subtitleTopOffset: CGFloat = 8 { 139 | didSet { 140 | subtitleOffsetConstraint?.constant = subtitleTopOffset 141 | } 142 | } 143 | 144 | /** 145 | separator offset from title or subtitle (if added) bottom anchor 146 | 147 | - Note: Actual for `ALRadioButtonStyle.standard` style 148 | */ 149 | public var separatorTopOffset: CGFloat = 8 { 150 | didSet { 151 | separatorOffsetConstraint?.constant = -separatorTopOffset 152 | } 153 | } 154 | 155 | // MARK: - Private Proporties 156 | private var itemHeightConstraint: NSLayoutConstraint? 157 | private var subtitleOffsetConstraint: NSLayoutConstraint? 158 | private var separatorOffsetConstraint: NSLayoutConstraint? 159 | private var style: ALRadioButtonStyle 160 | 161 | // MARK: - Life cycle 162 | public init(item: ALRadioItem, style: ALRadioButtonStyle) { 163 | self.style = style 164 | super.init(frame: .zero) 165 | titleLabel.text = item.title 166 | subtitleLabel.text = item.subtitle 167 | detailLabel.text = item.detail 168 | setupView() 169 | setupConstraints() 170 | } 171 | 172 | required init?(coder: NSCoder) { 173 | fatalError("init(coder:) has not been implemented") 174 | } 175 | } 176 | 177 | // MARK: - Layout Setup 178 | private extension ALRadioButton { 179 | func setupColors() { 180 | titleLabel.textColor = .black 181 | if isSelected { 182 | titleLabel.textColor = selectedTitleColor 183 | detailLabel.textColor = selectedDetailColor 184 | } else { 185 | titleLabel.textColor = unselectedTitleColor 186 | detailLabel.textColor = unselectedDetailColor 187 | } 188 | 189 | separatorView.backgroundColor = separatorColor 190 | } 191 | 192 | func setupView() { 193 | setupColors() 194 | 195 | layer.masksToBounds = true 196 | clipsToBounds = true 197 | 198 | titleStackView.axis = .horizontal 199 | titleStackView.alignment = .fill 200 | titleStackView.spacing = 12 201 | titleStackView.addArrangedSubview(radioIndicator) 202 | titleStackView.addArrangedSubview(titleLabel) 203 | titleStackView.addArrangedSubview(detailLabel) 204 | 205 | titleLabel.font = .systemFont(ofSize: 16, weight: .medium) 206 | titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) 207 | 208 | detailLabel.font = .systemFont(ofSize: 16, weight: .regular) 209 | detailLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) 210 | 211 | subtitleLabel.font = .systemFont(ofSize: 13, weight: .regular) 212 | subtitleLabel.numberOfLines = 0 213 | } 214 | 215 | func setupConstraints() { 216 | switch style { 217 | case .standard: standardStyle() 218 | case .grouped: groupedStyle() 219 | } 220 | } 221 | 222 | func standardStyle() { 223 | addSubview(itemView) 224 | itemView.addSubview(titleStackView) 225 | 226 | itemHeightConstraint = itemView.heightAnchor.constraint(equalToConstant: buttonHeight) 227 | itemView.translatesAutoresizingMaskIntoConstraints = false 228 | NSLayoutConstraint.activate([ 229 | itemHeightConstraint!, 230 | itemView.topAnchor.constraint(equalTo: self.topAnchor), 231 | itemView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 232 | itemView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 233 | ]) 234 | 235 | titleStackView.translatesAutoresizingMaskIntoConstraints = false 236 | NSLayoutConstraint.activate([ 237 | titleStackView.centerYAnchor.constraint(equalTo: itemView.centerYAnchor), 238 | titleStackView.leadingAnchor.constraint(equalTo: itemView.leadingAnchor), 239 | titleStackView.trailingAnchor.constraint(equalTo: itemView.trailingAnchor), 240 | ]) 241 | 242 | if subtitleLabel.text != nil { 243 | addSubview(subtitleLabel) 244 | subtitleOffsetConstraint = subtitleLabel.topAnchor.constraint(equalTo: itemView.bottomAnchor, constant: subtitleTopOffset) 245 | separatorOffsetConstraint = subtitleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -separatorTopOffset) 246 | subtitleLabel.translatesAutoresizingMaskIntoConstraints = false 247 | NSLayoutConstraint.activate([ 248 | subtitleOffsetConstraint!, 249 | subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), 250 | subtitleLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), 251 | separatorOffsetConstraint! 252 | ]) 253 | } else { 254 | separatorOffsetConstraint = itemView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: -separatorTopOffset) 255 | separatorOffsetConstraint?.isActive = true 256 | } 257 | 258 | addSubview(separatorView) 259 | separatorView.translatesAutoresizingMaskIntoConstraints = false 260 | NSLayoutConstraint.activate([ 261 | separatorView.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), 262 | separatorView.trailingAnchor.constraint(equalTo: titleStackView.trailingAnchor), 263 | separatorView.bottomAnchor.constraint(equalTo: self.bottomAnchor), 264 | separatorView.heightAnchor.constraint(equalToConstant: 0.5) 265 | ]) 266 | } 267 | 268 | func groupedStyle() { 269 | addSubview(itemView) 270 | itemView.addSubview(titleStackView) 271 | 272 | itemHeightConstraint = itemView.heightAnchor.constraint(equalToConstant: buttonHeight) 273 | itemView.translatesAutoresizingMaskIntoConstraints = false 274 | NSLayoutConstraint.activate([ 275 | itemHeightConstraint!, 276 | itemView.topAnchor.constraint(equalTo: self.topAnchor), 277 | itemView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 278 | itemView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 279 | itemView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor) 280 | ]) 281 | 282 | titleStackView.translatesAutoresizingMaskIntoConstraints = false 283 | NSLayoutConstraint.activate([ 284 | titleStackView.centerYAnchor.constraint(equalTo: itemView.centerYAnchor), 285 | titleStackView.leadingAnchor.constraint(equalTo: itemView.leadingAnchor, constant: 15), 286 | titleStackView.trailingAnchor.constraint(equalTo: itemView.trailingAnchor, constant: -15), 287 | ]) 288 | 289 | if subtitleLabel.text != nil { 290 | addSubview(subtitleLabel) 291 | 292 | subtitleOffsetConstraint = subtitleLabel.topAnchor.constraint(equalTo: itemView.bottomAnchor, constant: subtitleTopOffset) 293 | subtitleLabel.translatesAutoresizingMaskIntoConstraints = false 294 | NSLayoutConstraint.activate([ 295 | subtitleOffsetConstraint!, 296 | subtitleLabel.leadingAnchor.constraint(equalTo: radioIndicator.leadingAnchor), 297 | subtitleLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), 298 | subtitleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor) 299 | ]) 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /Sources/ALRadioButtons/Views/ALRadioGroup.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2020 Alexandr Guzenko (alxrguz@icloud.com) 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 | import UIKit 24 | 25 | 26 | /// `UIControl` element that combines `ALRadioButton` in a group. 27 | public final class ALRadioGroup: UIControl { 28 | // MARK: - UI Elements 29 | private lazy var itemsStackView = UIStackView() 30 | 31 | // MARK: - Open Proporties 32 | 33 | /** 34 | Index of selected item 35 | 36 | Default is -1 37 | 38 | - Note: If you do not set this value, then no item from the group will be selected 39 | */ 40 | public var selectedIndex: Int = -1 { 41 | didSet { 42 | item(at: oldValue)?.isSelected = false 43 | item(at: selectedIndex)?.isSelected = true 44 | } 45 | } 46 | 47 | /** 48 | If this parameter is set to true, then by tap on an already selected item, the selection will be canceled, and the `selectedIndex` will become `-1`. Default is false 49 | */ 50 | public var allowDeselection: Bool = false 51 | 52 | /** 53 | Title color if `ALRadioButton` is selected 54 | */ 55 | public var selectedTitleColor: UIColor = .systemBlue { 56 | didSet { 57 | items.forEach { $0.selectedTitleColor = selectedTitleColor } 58 | } 59 | } 60 | 61 | /** 62 | Title color if `ALRadioButton` is unselected 63 | */ 64 | public var unselectedTitleColor: UIColor = .black { 65 | didSet { 66 | items.forEach { $0.unselectedTitleColor = unselectedTitleColor } 67 | } 68 | } 69 | 70 | /** 71 | Detail color if `ALRadioButton` is selected 72 | */ 73 | public var selectedDetailColor: UIColor = .systemBlue { 74 | didSet { 75 | items.forEach { $0.selectedDetailColor = selectedDetailColor } 76 | } 77 | } 78 | 79 | /** 80 | Detail color if `ALRadioButton` is unselected 81 | */ 82 | public var unselectedDetailColor: UIColor = .lightGray { 83 | didSet { 84 | items.forEach { $0.unselectedDetailColor = unselectedDetailColor } 85 | } 86 | } 87 | 88 | /** 89 | Indicator (round ring with dot at center) color if `ALRadioButton` is selected 90 | */ 91 | public var selectedIndicatorColor: UIColor = .systemBlue { 92 | didSet { 93 | items.forEach { $0.selectedIndicatorColor = selectedIndicatorColor } 94 | } 95 | } 96 | 97 | /** 98 | Indicator (round ring with dot at center) color if `ALRadioButton` is unselected 99 | */ 100 | public var unselectedIndicatorColor: UIColor = .lightGray { 101 | didSet { 102 | items.forEach { $0.unselectedIndicatorColor = unselectedIndicatorColor } 103 | } 104 | } 105 | 106 | /** 107 | `ALRadioButton` subtitle color 108 | */ 109 | public var subtitleColor: UIColor = .lightGray { 110 | didSet { 111 | items.forEach { $0.subtitleLabel.textColor = subtitleColor } 112 | } 113 | } 114 | 115 | /** 116 | `ALRadioButton` background color 117 | 118 | - Note: Actual for `ALRadioButtonStyle.grouped` style 119 | */ 120 | public var buttonBackgroundColor: UIColor = .clear { 121 | didSet { 122 | items.forEach { $0.itemView.backgroundColor = buttonBackgroundColor } 123 | } 124 | } 125 | 126 | /** 127 | Separator color 128 | 129 | - Note: Actual for `ALRadioButtonStyle.standard` style 130 | */ 131 | public var separatorColor: UIColor = .lightGray { 132 | didSet { 133 | let lastItem = items.last 134 | items.forEach { 135 | $0.separatorColor = $0 == lastItem ? .clear : separatorColor 136 | } 137 | } 138 | } 139 | 140 | /** 141 | `ALRadioButton` title font 142 | */ 143 | public var titleFont: UIFont = .systemFont(ofSize: 16, weight: .medium) { 144 | didSet { 145 | items.forEach { $0.titleLabel.font = titleFont } 146 | } 147 | } 148 | 149 | 150 | /** 151 | `ALRadioButton` detail font 152 | */ 153 | public var detailFont: UIFont = .systemFont(ofSize: 16, weight: .regular) { 154 | didSet { 155 | items.forEach { $0.detailLabel.font = detailFont } 156 | } 157 | } 158 | 159 | /** 160 | `ALRadioButton` subtitle font 161 | */ 162 | public var subtitleFont: UIFont = .systemFont(ofSize: 13, weight: .regular) { 163 | didSet { 164 | items.forEach { $0.subtitleLabel.font = subtitleFont } 165 | } 166 | } 167 | 168 | /** 169 | `ALRadioButton` corner radius 170 | 171 | - Note: Actual for `ALRadioButtonStyle.grouped` style 172 | */ 173 | public var cornerRadius: CGFloat = 14 { 174 | didSet { 175 | items.forEach { $0.itemView.layer.cornerRadius = cornerRadius } 176 | } 177 | } 178 | 179 | /** 180 | `ALRadioButton` height 181 | 182 | - Note: View height at which indicator and title are located, subtitle is not included here 183 | */ 184 | public var buttonHeight: CGFloat = 50 { 185 | didSet { 186 | items.forEach { $0.buttonHeight = buttonHeight } 187 | } 188 | } 189 | 190 | /** 191 | `ALRadioButton` subtitle offset from title bottom anchor 192 | */ 193 | public var subtitleTopOffset: CGFloat = 8 { 194 | didSet { 195 | items.forEach { $0.subtitleTopOffset = subtitleTopOffset } 196 | } 197 | } 198 | 199 | /** 200 | `ALRadioButton` separator offset from title or subtitle (if added) bottom anchor 201 | 202 | - Note: Actual for `ALRadioButtonStyle.standard` style 203 | */ 204 | public var separatorTopOffset: CGFloat = 8 { 205 | didSet { 206 | items.forEach { $0.separatorTopOffset = separatorTopOffset } 207 | } 208 | } 209 | 210 | /** 211 | Outer ring width of indicator 212 | */ 213 | public var indicatorRingWidth: CGFloat = 2 { 214 | didSet { 215 | items.forEach { $0.radioIndicator.ringWidth = indicatorRingWidth } 216 | } 217 | } 218 | 219 | /** 220 | Indicator outer ring size 221 | */ 222 | public var indicatorRingSize: CGFloat = 22 { 223 | didSet { 224 | items.forEach { $0.radioIndicator.size = indicatorRingSize } 225 | } 226 | } 227 | 228 | /** 229 | Indentation of the circle from the outer ring 230 | */ 231 | public var indicatorCircleInset: CGFloat = 5 { 232 | didSet { 233 | items.forEach { $0.radioIndicator.selectedViewInset = indicatorCircleInset } 234 | } 235 | } 236 | 237 | /** 238 | `ALRadioButton` spacing 239 | */ 240 | public var spacing: CGFloat = 15 { 241 | didSet { 242 | itemsStackView.spacing = spacing 243 | } 244 | } 245 | 246 | /** 247 | The axis on which the `ALRadioButton` are located 248 | */ 249 | public var axis: NSLayoutConstraint.Axis = .vertical { 250 | didSet { 251 | itemsStackView.axis = axis 252 | 253 | switch axis { 254 | case .horizontal: 255 | itemsStackView.distribution = .fillEqually 256 | case .vertical: 257 | itemsStackView.distribution = .fill 258 | @unknown default: 259 | break 260 | } 261 | } 262 | } 263 | 264 | /** 265 | Haptic response when user clicks a button 266 | */ 267 | public var hapticResponse: Bool = true 268 | 269 | // MARK: - Private Proporties 270 | private var items: [ALRadioButton] { 271 | return itemsStackView.arrangedSubviews.compactMap { $0 as? ALRadioButton } 272 | } 273 | 274 | private let haptic = UISelectionFeedbackGenerator() 275 | 276 | // MARK: - Life cycle 277 | 278 | /// `ALRadioGroup` init 279 | /// - Parameters: 280 | /// - items: Array of `ALRadioItem`, one `ALRadioItem` is equivalent to one `ALRadioButton` 281 | /// - style: `ALRadioButton` style 282 | public init(items: [ALRadioItem], style: ALRadioButtonStyle = .standard) { 283 | super.init(frame: .zero) 284 | setupView(items: items, style: style) 285 | setupConstraints() 286 | } 287 | 288 | required init?(coder: NSCoder) { 289 | fatalError("init(coder:) has not been implemented") 290 | } 291 | } 292 | 293 | // MARK: - Handlers 294 | private extension ALRadioGroup { 295 | @objc func radioItemTapped(_ sender: UITapGestureRecognizer) { 296 | guard let radioItem = (sender.view as? ALRadioButton), 297 | var index = items.firstIndex(of: radioItem) else { return } 298 | 299 | if index == selectedIndex { 300 | if allowDeselection { 301 | index = -1 302 | } else { 303 | return 304 | } 305 | } 306 | 307 | if hapticResponse { haptic.selectionChanged() } 308 | selectedIndex = index 309 | sendActions(for: .valueChanged) 310 | } 311 | } 312 | 313 | // MARK: - Private Methods 314 | private extension ALRadioGroup { 315 | private func item(at index: Int) -> ALRadioButton? { 316 | guard index >= 0 && index < itemsStackView.arrangedSubviews.count else { return nil } 317 | return itemsStackView.arrangedSubviews[index] as? ALRadioButton 318 | } 319 | } 320 | 321 | // MARK: - Layout Setup 322 | private extension ALRadioGroup { 323 | func setupView(items: [ALRadioItem], style: ALRadioButtonStyle) { 324 | axis = .vertical 325 | itemsStackView.spacing = spacing 326 | 327 | items.forEach { 328 | let radioItem = ALRadioButton(item: $0, style: style) 329 | let tap = UITapGestureRecognizer(target: self, action: #selector(radioItemTapped(_:))) 330 | self.addGestureRecognizer(tap) 331 | radioItem.addGestureRecognizer(tap) 332 | itemsStackView.addArrangedSubview(radioItem) 333 | } 334 | 335 | basicAppearance(style: style) 336 | } 337 | 338 | func basicAppearance(style: ALRadioButtonStyle) { 339 | switch style { 340 | case .grouped: groupedAppearance() 341 | case .standard: standardAppearance() 342 | } 343 | 344 | } 345 | 346 | func groupedAppearance() { 347 | selectedIndicatorColor = .systemBlue 348 | selectedTitleColor = .systemBlue 349 | 350 | if #available(iOS 13.0, *) { 351 | buttonBackgroundColor = .secondarySystemGroupedBackground 352 | subtitleColor = .secondaryLabel 353 | unselectedIndicatorColor = .quaternaryLabel 354 | unselectedTitleColor = .label 355 | unselectedDetailColor = .secondaryLabel 356 | } else { 357 | buttonBackgroundColor = .white 358 | subtitleColor = .lightGray 359 | unselectedIndicatorColor = .lightGray 360 | unselectedTitleColor = .black 361 | unselectedDetailColor = .lightGray 362 | } 363 | 364 | titleFont = .systemFont(ofSize: 16, weight: .medium) 365 | subtitleFont = .systemFont(ofSize: 13, weight: .regular) 366 | subtitleTopOffset = 8 367 | cornerRadius = 14 368 | buttonHeight = 50 369 | 370 | indicatorRingSize = 22 371 | indicatorRingWidth = 2 372 | indicatorCircleInset = 5 373 | } 374 | 375 | func standardAppearance() { 376 | selectedIndicatorColor = .systemBlue 377 | selectedTitleColor = .systemBlue 378 | buttonBackgroundColor = .clear 379 | 380 | if #available(iOS 13.0, *) { 381 | subtitleColor = .secondaryLabel 382 | unselectedIndicatorColor = .quaternaryLabel 383 | separatorColor = .quaternaryLabel 384 | unselectedTitleColor = .label 385 | unselectedDetailColor = .secondaryLabel 386 | } else { 387 | subtitleColor = .lightGray 388 | unselectedIndicatorColor = .lightGray 389 | unselectedTitleColor = .black 390 | separatorColor = .lightGray 391 | unselectedDetailColor = .lightGray 392 | } 393 | 394 | titleFont = .systemFont(ofSize: 16, weight: .medium) 395 | subtitleFont = .systemFont(ofSize: 13, weight: .regular) 396 | subtitleTopOffset = 0 397 | cornerRadius = 0 398 | buttonHeight = 30 399 | separatorTopOffset = 8 400 | 401 | indicatorRingSize = 22 402 | indicatorRingWidth = 2 403 | indicatorCircleInset = 5 404 | } 405 | 406 | func setupConstraints() { 407 | addSubview(itemsStackView) 408 | 409 | itemsStackView.translatesAutoresizingMaskIntoConstraints = false 410 | NSLayoutConstraint.activate ([ 411 | itemsStackView.topAnchor.constraint(equalTo: self.topAnchor), 412 | itemsStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), 413 | itemsStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 414 | itemsStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor) 415 | ]) 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /ExampleApp/ALRadioButtons Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BB19EEBF26B3F0E100CF3FE6 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = BB19EEBE26B3F0E100CF3FE6 /* SnapKit */; }; 11 | BB19EEC126B3F11A00CF3FE6 /* ALHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB19EEC026B3F11A00CF3FE6 /* ALHeaderView.swift */; }; 12 | BB3959F724EA71EE00B965F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3959F624EA71EE00B965F5 /* AppDelegate.swift */; }; 13 | BB3959FB24EA71EE00B965F5 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3959FA24EA71EE00B965F5 /* MainViewController.swift */; }; 14 | BB395A0024EA71F200B965F5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BB3959FF24EA71F200B965F5 /* Assets.xcassets */; }; 15 | BB395A0324EA71F200B965F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB395A0124EA71F200B965F5 /* LaunchScreen.storyboard */; }; 16 | BB4DE09026D69E7600EE0206 /* RadioGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4DE08F26D69E7600EE0206 /* RadioGroupViewController.swift */; }; 17 | BBFF0BE026B3EA2800E7A4D0 /* ALRadioButtons in Frameworks */ = {isa = PBXBuildFile; productRef = BBFF0BDF26B3EA2800E7A4D0 /* ALRadioButtons */; }; 18 | BBFF0BE526B3EB7900E7A4D0 /* ALScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFF0BE426B3EB7900E7A4D0 /* ALScrollView.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | BB19EEC026B3F11A00CF3FE6 /* ALHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALHeaderView.swift; sourceTree = ""; }; 23 | BB3959F324EA71EE00B965F5 /* iOS App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | BB3959F624EA71EE00B965F5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | BB3959FA24EA71EE00B965F5 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 26 | BB3959FF24EA71F200B965F5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | BB395A0224EA71F200B965F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | BB395A0424EA71F200B965F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | BB4DE08F26D69E7600EE0206 /* RadioGroupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioGroupViewController.swift; sourceTree = ""; }; 30 | BBFF0BDE26B3EA0E00E7A4D0 /* ALRadioButtons */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ALRadioButtons; path = ..; sourceTree = ""; }; 31 | BBFF0BE426B3EB7900E7A4D0 /* ALScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALScrollView.swift; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | BB3959F024EA71EE00B965F5 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | BBFF0BE026B3EA2800E7A4D0 /* ALRadioButtons in Frameworks */, 40 | BB19EEBF26B3F0E100CF3FE6 /* SnapKit in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | BB3959EA24EA71EE00B965F5 = { 48 | isa = PBXGroup; 49 | children = ( 50 | BBFF0BDE26B3EA0E00E7A4D0 /* ALRadioButtons */, 51 | BB3959F524EA71EE00B965F5 /* iOS App */, 52 | BB3959F424EA71EE00B965F5 /* Products */, 53 | BB65E1FD266669ED009B68D4 /* Frameworks */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | BB3959F424EA71EE00B965F5 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | BB3959F324EA71EE00B965F5 /* iOS App.app */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | BB3959F524EA71EE00B965F5 /* iOS App */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | BB3959F624EA71EE00B965F5 /* AppDelegate.swift */, 69 | BB395A0A24EA722A00B965F5 /* Controllers */, 70 | BB395A0B24EA723800B965F5 /* Sources */, 71 | ); 72 | path = "iOS App"; 73 | sourceTree = ""; 74 | }; 75 | BB395A0A24EA722A00B965F5 /* Controllers */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | BB395A0D24EA727000B965F5 /* Launch */, 79 | BB3959FA24EA71EE00B965F5 /* MainViewController.swift */, 80 | BB4DE08F26D69E7600EE0206 /* RadioGroupViewController.swift */, 81 | ); 82 | path = Controllers; 83 | sourceTree = ""; 84 | }; 85 | BB395A0B24EA723800B965F5 /* Sources */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | BBFF0BE326B3EB4F00E7A4D0 /* Views */, 89 | BB3959FF24EA71F200B965F5 /* Assets.xcassets */, 90 | BB395A0424EA71F200B965F5 /* Info.plist */, 91 | ); 92 | path = Sources; 93 | sourceTree = ""; 94 | }; 95 | BB395A0D24EA727000B965F5 /* Launch */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | BB395A0124EA71F200B965F5 /* LaunchScreen.storyboard */, 99 | ); 100 | path = Launch; 101 | sourceTree = ""; 102 | }; 103 | BB65E1FD266669ED009B68D4 /* Frameworks */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | ); 107 | name = Frameworks; 108 | sourceTree = ""; 109 | }; 110 | BBFF0BE326B3EB4F00E7A4D0 /* Views */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | BB19EEC026B3F11A00CF3FE6 /* ALHeaderView.swift */, 114 | BBFF0BE426B3EB7900E7A4D0 /* ALScrollView.swift */, 115 | ); 116 | path = Views; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | BB3959F224EA71EE00B965F5 /* iOS App */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = BB395A0724EA71F200B965F5 /* Build configuration list for PBXNativeTarget "iOS App" */; 125 | buildPhases = ( 126 | BB3959EF24EA71EE00B965F5 /* Sources */, 127 | BB3959F024EA71EE00B965F5 /* Frameworks */, 128 | BB3959F124EA71EE00B965F5 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = "iOS App"; 135 | packageProductDependencies = ( 136 | BBFF0BDF26B3EA2800E7A4D0 /* ALRadioButtons */, 137 | BB19EEBE26B3F0E100CF3FE6 /* SnapKit */, 138 | ); 139 | productName = "Panda School"; 140 | productReference = BB3959F324EA71EE00B965F5 /* iOS App.app */; 141 | productType = "com.apple.product-type.application"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | BB3959EB24EA71EE00B965F5 /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastSwiftUpdateCheck = 1160; 150 | LastUpgradeCheck = 1160; 151 | ORGANIZATIONNAME = alxrguz; 152 | TargetAttributes = { 153 | BB3959F224EA71EE00B965F5 = { 154 | CreatedOnToolsVersion = 11.6; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = BB3959EE24EA71EE00B965F5 /* Build configuration list for PBXProject "ALRadioButtons Example" */; 159 | compatibilityVersion = "Xcode 9.3"; 160 | developmentRegion = en; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = BB3959EA24EA71EE00B965F5; 167 | packageReferences = ( 168 | BB19EEBD26B3F0E100CF3FE6 /* XCRemoteSwiftPackageReference "SnapKit" */, 169 | ); 170 | productRefGroup = BB3959F424EA71EE00B965F5 /* Products */; 171 | projectDirPath = ""; 172 | projectRoot = ""; 173 | targets = ( 174 | BB3959F224EA71EE00B965F5 /* iOS App */, 175 | ); 176 | }; 177 | /* End PBXProject section */ 178 | 179 | /* Begin PBXResourcesBuildPhase section */ 180 | BB3959F124EA71EE00B965F5 /* Resources */ = { 181 | isa = PBXResourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | BB395A0324EA71F200B965F5 /* LaunchScreen.storyboard in Resources */, 185 | BB395A0024EA71F200B965F5 /* Assets.xcassets in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXResourcesBuildPhase section */ 190 | 191 | /* Begin PBXSourcesBuildPhase section */ 192 | BB3959EF24EA71EE00B965F5 /* Sources */ = { 193 | isa = PBXSourcesBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | BB3959FB24EA71EE00B965F5 /* MainViewController.swift in Sources */, 197 | BB3959F724EA71EE00B965F5 /* AppDelegate.swift in Sources */, 198 | BB4DE09026D69E7600EE0206 /* RadioGroupViewController.swift in Sources */, 199 | BB19EEC126B3F11A00CF3FE6 /* ALHeaderView.swift in Sources */, 200 | BBFF0BE526B3EB7900E7A4D0 /* ALScrollView.swift in Sources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXSourcesBuildPhase section */ 205 | 206 | /* Begin PBXVariantGroup section */ 207 | BB395A0124EA71F200B965F5 /* LaunchScreen.storyboard */ = { 208 | isa = PBXVariantGroup; 209 | children = ( 210 | BB395A0224EA71F200B965F5 /* Base */, 211 | ); 212 | name = LaunchScreen.storyboard; 213 | sourceTree = ""; 214 | }; 215 | /* End PBXVariantGroup section */ 216 | 217 | /* Begin XCBuildConfiguration section */ 218 | BB395A0524EA71F200B965F5 /* Debug */ = { 219 | isa = XCBuildConfiguration; 220 | buildSettings = { 221 | ALWAYS_SEARCH_USER_PATHS = NO; 222 | CLANG_ANALYZER_NONNULL = YES; 223 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 224 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 225 | CLANG_CXX_LIBRARY = "libc++"; 226 | CLANG_ENABLE_MODULES = YES; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | CLANG_ENABLE_OBJC_WEAK = YES; 229 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_COMMA = YES; 232 | CLANG_WARN_CONSTANT_CONVERSION = YES; 233 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 234 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 235 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 236 | CLANG_WARN_EMPTY_BODY = YES; 237 | CLANG_WARN_ENUM_CONVERSION = YES; 238 | CLANG_WARN_INFINITE_RECURSION = YES; 239 | CLANG_WARN_INT_CONVERSION = YES; 240 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 242 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 244 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 245 | CLANG_WARN_STRICT_PROTOTYPES = YES; 246 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 247 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 248 | CLANG_WARN_UNREACHABLE_CODE = YES; 249 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = dwarf; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | ENABLE_TESTABILITY = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu11; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 269 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 270 | MTL_FAST_MATH = YES; 271 | ONLY_ACTIVE_ARCH = YES; 272 | SDKROOT = iphoneos; 273 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 274 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 275 | }; 276 | name = Debug; 277 | }; 278 | BB395A0624EA71F200B965F5 /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_NONNULL = YES; 283 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_ENABLE_OBJC_WEAK = YES; 289 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_COMMA = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | COPY_PHASE_STRIP = NO; 311 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 312 | ENABLE_NS_ASSERTIONS = NO; 313 | ENABLE_STRICT_OBJC_MSGSEND = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu11; 315 | GCC_NO_COMMON_BLOCKS = YES; 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 318 | GCC_WARN_UNDECLARED_SELECTOR = YES; 319 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 320 | GCC_WARN_UNUSED_FUNCTION = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 323 | MTL_ENABLE_DEBUG_INFO = NO; 324 | MTL_FAST_MATH = YES; 325 | SDKROOT = iphoneos; 326 | SWIFT_COMPILATION_MODE = wholemodule; 327 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 328 | VALIDATE_PRODUCT = YES; 329 | }; 330 | name = Release; 331 | }; 332 | BB395A0824EA71F200B965F5 /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 336 | CODE_SIGN_STYLE = Automatic; 337 | DEVELOPMENT_TEAM = RW83L2JUQB; 338 | INFOPLIST_FILE = "iOS App/Sources/Info.plist"; 339 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 340 | LD_RUNPATH_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "@executable_path/Frameworks", 343 | ); 344 | PRODUCT_BUNDLE_IDENTIFIER = alxrguz.alradiobuttons.example; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_VERSION = 5.0; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | }; 349 | name = Debug; 350 | }; 351 | BB395A0924EA71F200B965F5 /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | CODE_SIGN_STYLE = Automatic; 356 | DEVELOPMENT_TEAM = RW83L2JUQB; 357 | INFOPLIST_FILE = "iOS App/Sources/Info.plist"; 358 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 359 | LD_RUNPATH_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "@executable_path/Frameworks", 362 | ); 363 | PRODUCT_BUNDLE_IDENTIFIER = alxrguz.alradiobuttons.example; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | SWIFT_VERSION = 5.0; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Release; 369 | }; 370 | /* End XCBuildConfiguration section */ 371 | 372 | /* Begin XCConfigurationList section */ 373 | BB3959EE24EA71EE00B965F5 /* Build configuration list for PBXProject "ALRadioButtons Example" */ = { 374 | isa = XCConfigurationList; 375 | buildConfigurations = ( 376 | BB395A0524EA71F200B965F5 /* Debug */, 377 | BB395A0624EA71F200B965F5 /* Release */, 378 | ); 379 | defaultConfigurationIsVisible = 0; 380 | defaultConfigurationName = Release; 381 | }; 382 | BB395A0724EA71F200B965F5 /* Build configuration list for PBXNativeTarget "iOS App" */ = { 383 | isa = XCConfigurationList; 384 | buildConfigurations = ( 385 | BB395A0824EA71F200B965F5 /* Debug */, 386 | BB395A0924EA71F200B965F5 /* Release */, 387 | ); 388 | defaultConfigurationIsVisible = 0; 389 | defaultConfigurationName = Release; 390 | }; 391 | /* End XCConfigurationList section */ 392 | 393 | /* Begin XCRemoteSwiftPackageReference section */ 394 | BB19EEBD26B3F0E100CF3FE6 /* XCRemoteSwiftPackageReference "SnapKit" */ = { 395 | isa = XCRemoteSwiftPackageReference; 396 | repositoryURL = "https://github.com/SnapKit/SnapKit"; 397 | requirement = { 398 | kind = upToNextMajorVersion; 399 | minimumVersion = 5.0.1; 400 | }; 401 | }; 402 | /* End XCRemoteSwiftPackageReference section */ 403 | 404 | /* Begin XCSwiftPackageProductDependency section */ 405 | BB19EEBE26B3F0E100CF3FE6 /* SnapKit */ = { 406 | isa = XCSwiftPackageProductDependency; 407 | package = BB19EEBD26B3F0E100CF3FE6 /* XCRemoteSwiftPackageReference "SnapKit" */; 408 | productName = SnapKit; 409 | }; 410 | BBFF0BDF26B3EA2800E7A4D0 /* ALRadioButtons */ = { 411 | isa = XCSwiftPackageProductDependency; 412 | productName = ALRadioButtons; 413 | }; 414 | /* End XCSwiftPackageProductDependency section */ 415 | }; 416 | rootObject = BB3959EB24EA71EE00B965F5 /* Project object */; 417 | } 418 | --------------------------------------------------------------------------------