Skip to content

Commit a04cdae

Browse files
authored
Merge pull request Boris-Em#39 from Vortec4800/groups
Added a new group feature for radio button functionality
2 parents e880db8 + 7a8377f commit a04cdae

File tree

6 files changed

+234
-2
lines changed

6 files changed

+234
-2
lines changed

Classes/BEMCheckBox.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import <UIKit/UIKit.h>
1010

11+
@class BEMCheckBoxGroup;
1112
@protocol BEMCheckBoxDelegate;
1213

1314
/** The different type of boxes available.
@@ -108,6 +109,10 @@ typedef NS_ENUM(NSInteger, BEMAnimationType) {
108109
*/
109110
@property (strong, nonatomic) IBInspectable UIColor *tintColor;
110111

112+
/** The group this box is associated with.
113+
*/
114+
@property (weak, nonatomic, nullable, readonly) BEMCheckBoxGroup *group;
115+
111116
/** The type of box.
112117
* @see BEMBoxType.
113118
*/

Classes/BEMCheckBox.m

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import "BEMCheckBox.h"
1010
#import "BEMAnimationManager.h"
1111
#import "BEMPathManager.h"
12+
#import "BEMCheckBoxGroup.h"
1213

1314
@interface BEMCheckBox ()
1415

@@ -32,6 +33,18 @@ @interface BEMCheckBox ()
3233
*/
3334
@property (strong, nonatomic) BEMPathManager *pathManager;
3435

36+
/** The group this box is associated with.
37+
*/
38+
@property (weak, nonatomic, nullable) BEMCheckBoxGroup *group;
39+
40+
@end
41+
42+
/** Defines private methods that we can call to update our group's status.
43+
*/
44+
@interface BEMCheckBoxGroup ()
45+
46+
- (void)_checkBoxSelectionChanged:(BEMCheckBox *)checkBox;
47+
3548
@end
3649

3750
@implementation BEMCheckBox
@@ -101,7 +114,7 @@ - (void)reload {
101114
}
102115

103116
#pragma mark Setters
104-
- (void)setOn:(BOOL)on animated:(BOOL)animated {
117+
- (void)_setOn:(BOOL)on animated:(BOOL)animated notifyGroup:(BOOL)notifyGroup {
105118
_on = on;
106119

107120
[self drawEntireCheckBox];
@@ -118,6 +131,14 @@ - (void)setOn:(BOOL)on animated:(BOOL)animated {
118131
[self.checkMarkLayer removeFromSuperlayer];
119132
}
120133
}
134+
135+
if(notifyGroup){
136+
[self.group _checkBoxSelectionChanged:self];
137+
}
138+
}
139+
140+
- (void)setOn:(BOOL)on animated:(BOOL)animated {
141+
[self _setOn:on animated:animated notifyGroup:YES];
121142
}
122143

123144
- (void)setOn:(BOOL)on {
@@ -167,6 +188,11 @@ - (void)setOnCheckColor:(UIColor *)onCheckColor {
167188

168189
#pragma mark Gesture Recognizer
169190
- (void)handleTapCheckBox:(UITapGestureRecognizer *)recognizer {
191+
// If we have a group that requires a selection, and we're already selected, don't allow a deselection
192+
if(self.group && self.group.mustHaveSelection && self.on){
193+
return;
194+
}
195+
170196
[self setOn:!self.on animated:YES];
171197
if ([self.delegate respondsToSelector:@selector(didTapCheckBox:)]) {
172198
[self.delegate didTapCheckBox:self];

Classes/BEMCheckBoxGroup.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// BEMCheckBoxGroup.h
3+
// CheckBox
4+
//
5+
// Created by Cory Imdieke on 10/17/16.
6+
// Copyright © 2016 Boris Emorine. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
11+
@class BEMCheckBox;
12+
13+
@interface BEMCheckBoxGroup : NSObject
14+
15+
/** An array of check boxes in this group.
16+
*/
17+
@property (nonatomic, strong, nonnull, readonly) NSOrderedSet<BEMCheckBox *> *checkBoxes;
18+
19+
/** The currently selected check box. Only can be nil if mustHaveSelection is NO. Setting this value will cause the other check boxes to deselect automatically.
20+
*/
21+
@property (nonatomic, strong, nullable) BEMCheckBox *selectedCheckBox;
22+
23+
/** If YES, don't allow the user to unselect all options, must have single selection at all times. Default to NO.
24+
*/
25+
@property (nonatomic) BOOL mustHaveSelection;
26+
27+
/** Creates a new group with the list of check boxes.
28+
*/
29+
+ (nonnull instancetype)groupWithCheckBoxes:(nullable NSArray<BEMCheckBox *> *)checkBoxes;
30+
31+
/** Adds a check box to this group. Check boxes can only belong to a single group, adding to a group removes it from its current group.
32+
*/
33+
- (void)addCheckBoxToGroup:(nonnull BEMCheckBox *)checkBox;
34+
35+
/** Removes a check box from this group.
36+
*/
37+
- (void)removeCheckBoxFromGroup:(nonnull BEMCheckBox *)checkBox;
38+
39+
@end

Classes/BEMCheckBoxGroup.m

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//
2+
// BEMCheckBoxGroup.m
3+
// CheckBox
4+
//
5+
// Created by Cory Imdieke on 10/17/16.
6+
// Copyright © 2016 Boris Emorine. All rights reserved.
7+
//
8+
9+
#import "BEMCheckBoxGroup.h"
10+
#import "BEMCheckBox.h"
11+
12+
@interface BEMCheckBoxGroup ()
13+
14+
@property (nonatomic, strong, nonnull) NSOrderedSet<BEMCheckBox *> *checkBoxes;
15+
16+
@end
17+
18+
/** Defines private methods that we can call on the check box.
19+
*/
20+
@interface BEMCheckBox ()
21+
22+
@property (weak, nonatomic, nullable) BEMCheckBoxGroup *group;
23+
24+
- (void)_setOn:(BOOL)on animated:(BOOL)animated notifyGroup:(BOOL)notifyGroup;
25+
26+
@end
27+
28+
@implementation BEMCheckBoxGroup
29+
30+
- (instancetype)init {
31+
self = [super init];
32+
if (self) {
33+
_mustHaveSelection = NO;
34+
_checkBoxes = [NSOrderedSet orderedSet];
35+
}
36+
return self;
37+
}
38+
39+
+ (nonnull instancetype)groupWithCheckBoxes:(nullable NSArray<BEMCheckBox *> *)checkBoxes {
40+
BEMCheckBoxGroup *group = [[BEMCheckBoxGroup alloc] init];
41+
for (BEMCheckBox *checkbox in checkBoxes) {
42+
[group addCheckBoxToGroup:checkbox];
43+
}
44+
45+
return group;
46+
}
47+
48+
- (void)addCheckBoxToGroup:(nonnull BEMCheckBox *)checkBox {
49+
if (checkBox.group) {
50+
[checkBox.group removeCheckBoxFromGroup:checkBox];
51+
}
52+
53+
[checkBox _setOn:NO animated:NO notifyGroup:NO];
54+
checkBox.group = self;
55+
NSMutableOrderedSet *mutableBoxes = [self.checkBoxes mutableCopy];
56+
[mutableBoxes addObject:checkBox];
57+
self.checkBoxes = [NSOrderedSet orderedSetWithOrderedSet:mutableBoxes];
58+
}
59+
60+
- (void)removeCheckBoxFromGroup:(nonnull BEMCheckBox *)checkBox {
61+
if (![self.checkBoxes containsObject:checkBox]) {
62+
// Not in this group
63+
return;
64+
}
65+
66+
checkBox.group = nil;
67+
NSMutableOrderedSet *mutableBoxes = [self.checkBoxes mutableCopy];
68+
[mutableBoxes removeObject:checkBox];
69+
self.checkBoxes = [NSOrderedSet orderedSetWithOrderedSet:mutableBoxes];
70+
}
71+
72+
#pragma mark Getters
73+
74+
- (BEMCheckBox *)selectedCheckBox {
75+
BEMCheckBox *selected = nil;
76+
for (BEMCheckBox *checkBox in self.checkBoxes) {
77+
if(checkBox.on){
78+
selected = checkBox;
79+
break;
80+
}
81+
}
82+
83+
return selected;
84+
}
85+
86+
#pragma mark Setters
87+
88+
- (void)setSelectedCheckBox:(BEMCheckBox *)selectedCheckBox {
89+
if (selectedCheckBox) {
90+
for (BEMCheckBox *checkBox in self.checkBoxes) {
91+
BOOL shouldBeOn = (checkBox == selectedCheckBox);
92+
if(checkBox.on != shouldBeOn){
93+
[checkBox _setOn:shouldBeOn animated:YES notifyGroup:NO];
94+
}
95+
}
96+
} else {
97+
// Selection is nil
98+
if(self.mustHaveSelection && [self.checkBoxes count] > 0){
99+
// We must have a selected checkbox, so re-call this method with the first checkbox
100+
self.selectedCheckBox = [self.checkBoxes firstObject];
101+
} else {
102+
for (BEMCheckBox *checkBox in self.checkBoxes) {
103+
BOOL shouldBeOn = NO;
104+
if(checkBox.on != shouldBeOn){
105+
[checkBox _setOn:shouldBeOn animated:YES notifyGroup:NO];
106+
}
107+
}
108+
}
109+
}
110+
}
111+
112+
- (void)setMustHaveSelection:(BOOL)mustHaveSelection {
113+
_mustHaveSelection = mustHaveSelection;
114+
115+
// If it must have a selection and we currently don't, select the first box
116+
if (mustHaveSelection && !self.selectedCheckBox) {
117+
self.selectedCheckBox = [self.checkBoxes firstObject];
118+
}
119+
}
120+
121+
#pragma mark Private methods called by BEMCheckBox
122+
123+
- (void)_checkBoxSelectionChanged:(BEMCheckBox *)checkBox {
124+
if ([checkBox on]) {
125+
// Change selected checkbox to this one
126+
self.selectedCheckBox = checkBox;
127+
} else if(checkBox == self.selectedCheckBox) {
128+
// Selected checkbox was this one, clear it
129+
self.selectedCheckBox = nil;
130+
}
131+
}
132+
133+
@end

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* [**Documentation**](#documentation)
2424
* [Enabling / Disabling the Checkbox](#enabling--disabling-the-checkbox)
2525
* [Reloading](#reloading)
26+
* [Group / Radio Button Functionality](#group--radio-button-functionality)
2627
* [Delegate] (#delegate)
2728
* [Customization](#customization)
2829

@@ -116,6 +117,25 @@ Example usage:
116117
[self.myCheckBox reload]
117118
```
118119

120+
### Group / Radio Button Functionality
121+
**BEMCheckBox**es can be easily grouped together to form radio button functionality. This will automatically manage the state of each checkbox in the group so that only one is selected at a time, and can optionally require that the group has a selection at all times.
122+
123+
```objective-c
124+
self.group = [BEMCheckBoxGroup groupWithCheckBoxes:@[self.checkBox1, self.checkBox2, self.checkBox3]];
125+
self.group.selectedCheckBox = self.checkBox2; // Optionally set which checkbox is pre-selected
126+
self.group.mustHaveSelection = YES; // Define if the group must always have a selection
127+
```
128+
129+
To see which checkbox is selected in that group, just ask for it:
130+
```objective-c
131+
BEMCheckBox *selection = self.group.selectedCheckBox;
132+
```
133+
134+
To manually update the selection for a group, just set it:
135+
```objective-c
136+
self.group.selectedCheckBox = self.checkBox1;
137+
```
138+
119139
### Delegate
120140
**BEMCheckBox** uses a delegate to receive check box events. The delegate object must conform to the `BEMCheckBoxDelegate` protocol, which is composed of two optional methods:
121141

@@ -184,4 +204,3 @@ The possible values for `onAnimationType` and `offAnimationType`.
184204

185205
- `BEMAnimationTypeFade`
186206
<p align="left"><img src="http://s24.postimg.org/3n1rre1cx/BEMAnimation_Type_Fade.gif"/></p>
187-

Sample Project/CheckBox.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
5643F13B1CDE724A0020E238 /* BEMAnimationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E837A91BAE3493004576D6 /* BEMAnimationManager.m */; };
1616
5643F13C1CDE724A0020E238 /* BEMPathManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E837AB1BAE35D8004576D6 /* BEMPathManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
1717
5643F13D1CDE724A0020E238 /* BEMPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E837AC1BAE35D8004576D6 /* BEMPathManager.m */; };
18+
984F9E0F1DB59228002F746B /* BEMCheckBoxGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 984F9E0D1DB59228002F746B /* BEMCheckBoxGroup.h */; };
19+
984F9E101DB59228002F746B /* BEMCheckBoxGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 984F9E0E1DB59228002F746B /* BEMCheckBoxGroup.m */; };
20+
984F9E111DB59228002F746B /* BEMCheckBoxGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 984F9E0E1DB59228002F746B /* BEMCheckBoxGroup.m */; };
1821
C32537131B9231780059F394 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C32537121B9231780059F394 /* main.m */; };
1922
C32537161B9231780059F394 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C32537151B9231780059F394 /* AppDelegate.m */; };
2023
C325371C1B9231780059F394 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C325371A1B9231780059F394 /* Main.storyboard */; };
@@ -70,6 +73,8 @@
7073
5643F12B1CDE722C0020E238 /* BEMCheckBox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BEMCheckBox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7174
5643F12D1CDE722C0020E238 /* CheckBox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CheckBox.h; sourceTree = "<group>"; };
7275
5643F12F1CDE722C0020E238 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
76+
984F9E0D1DB59228002F746B /* BEMCheckBoxGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BEMCheckBoxGroup.h; path = ../Classes/BEMCheckBoxGroup.h; sourceTree = "<group>"; };
77+
984F9E0E1DB59228002F746B /* BEMCheckBoxGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BEMCheckBoxGroup.m; path = ../Classes/BEMCheckBoxGroup.m; sourceTree = "<group>"; };
7378
C325370D1B9231780059F394 /* CheckBox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CheckBox.app; sourceTree = BUILT_PRODUCTS_DIR; };
7479
C32537111B9231780059F394 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7580
C32537121B9231780059F394 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -219,6 +224,8 @@
219224
C3E837A91BAE3493004576D6 /* BEMAnimationManager.m */,
220225
C3E837AB1BAE35D8004576D6 /* BEMPathManager.h */,
221226
C3E837AC1BAE35D8004576D6 /* BEMPathManager.m */,
227+
984F9E0D1DB59228002F746B /* BEMCheckBoxGroup.h */,
228+
984F9E0E1DB59228002F746B /* BEMCheckBoxGroup.m */,
222229
);
223230
name = Classes;
224231
sourceTree = "<group>";
@@ -239,6 +246,7 @@
239246
isa = PBXHeadersBuildPhase;
240247
buildActionMask = 2147483647;
241248
files = (
249+
984F9E0F1DB59228002F746B /* BEMCheckBoxGroup.h in Headers */,
242250
5643F1381CDE724A0020E238 /* BEMCheckBox.h in Headers */,
243251
5643F13C1CDE724A0020E238 /* BEMPathManager.h in Headers */,
244252
5643F13A1CDE724A0020E238 /* BEMAnimationManager.h in Headers */,
@@ -408,6 +416,7 @@
408416
buildActionMask = 2147483647;
409417
files = (
410418
5643F1391CDE724A0020E238 /* BEMCheckBox.m in Sources */,
419+
984F9E111DB59228002F746B /* BEMCheckBoxGroup.m in Sources */,
411420
5643F13D1CDE724A0020E238 /* BEMPathManager.m in Sources */,
412421
5643F13B1CDE724A0020E238 /* BEMAnimationManager.m in Sources */,
413422
);
@@ -420,6 +429,7 @@
420429
C39F1AB71BAFEAA400E8A023 /* BEMMainViewController.m in Sources */,
421430
C3DFB9BD1BBD0E2800D2F8B4 /* BEMAnimationsTableViewController.m in Sources */,
422431
C32537161B9231780059F394 /* AppDelegate.m in Sources */,
432+
984F9E101DB59228002F746B /* BEMCheckBoxGroup.m in Sources */,
423433
C32537131B9231780059F394 /* main.m in Sources */,
424434
C3E594901BC220C7005EA38B /* BEMCurrentSetupTableViewController.m in Sources */,
425435
);

0 commit comments

Comments
 (0)