├── Pod
├── Assets
│ └── .gitkeep
└── Classes
│ ├── .gitkeep
│ ├── Fields
│ ├── TLFormFieldImage.h
│ ├── TLFormFieldTitle.h
│ ├── TLFormFieldMultiLine.h
│ ├── TLFormFieldYesNo.h
│ ├── TLFormFieldNumeric.h
│ ├── TLFormFieldSelect.h
│ ├── TLFormFieldDateTime.h
│ ├── TLFormAllFields.h
│ ├── TLFormFieldSingleLine.h
│ ├── TLFormField+Protected.h
│ ├── TLFormFieldList.h
│ ├── TLFormFieldTitle.m
│ ├── TLFormFieldNumeric.m
│ ├── TLFormField.h
│ ├── TLFormFieldYesNo.m
│ ├── TLFormFieldSelect.m
│ ├── TLFormFieldDateTime.m
│ ├── TLFormFieldMultiLine.m
│ ├── TLFormFieldSingleLine.m
│ ├── TLFormFieldList.m
│ ├── TLFormField.m
│ └── TLFormFieldImage.m
│ ├── Extensions
│ ├── NSNumber+NumberType.h
│ └── NSNumber+NumberType.m
│ ├── Style
│ ├── TLFormField+UIAppearance.h
│ └── TLFormField+UIAppearance.m
│ ├── TLFormModel.h
│ ├── TLFormView.h
│ ├── TLFormModel.m
│ └── TLFormView.m
├── Screenshots
├── ipad_ex_1.png
├── in-place_help.png
├── iphone_ex_1.png
└── iphone_quick_ex.png
├── Example
├── TLFormView.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── project.pbxproj
└── TLFormView
│ ├── ViewController.h
│ ├── AppDelegate.h
│ ├── main.m
│ ├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Info.plist
│ ├── AppDelegate.m
│ ├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
│ └── ViewController.m
├── .gitignore
├── TLFormView.podspec
├── LICENSE
└── README.md
/Pod/Assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Pod/Classes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Screenshots/ipad_ex_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryolabs/TLFormView/HEAD/Screenshots/ipad_ex_1.png
--------------------------------------------------------------------------------
/Screenshots/in-place_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryolabs/TLFormView/HEAD/Screenshots/in-place_help.png
--------------------------------------------------------------------------------
/Screenshots/iphone_ex_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryolabs/TLFormView/HEAD/Screenshots/iphone_ex_1.png
--------------------------------------------------------------------------------
/Screenshots/iphone_quick_ex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryolabs/TLFormView/HEAD/Screenshots/iphone_quick_ex.png
--------------------------------------------------------------------------------
/Example/TLFormView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldImage.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldImage.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 |
11 | @interface TLFormFieldImage : TLFormField
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldTitle.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldTitle.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 |
11 | @interface TLFormFieldTitle : TLFormField
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/TLFormView/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/23/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UIViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldMultiLine.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldMultiLine.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 |
11 | @interface TLFormFieldMultiLine : TLFormField
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldYesNo.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldYesNo.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldSingleLine.h"
10 |
11 | @interface TLFormFieldYesNo : TLFormFieldSingleLine
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldNumeric.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldNumeric.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldSingleLine.h"
10 |
11 | @interface TLFormFieldNumeric : TLFormFieldSingleLine
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/TLFormView/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/23/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/Example/TLFormView/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/23/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldSelect.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldSelect.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldSingleLine.h"
10 |
11 | @interface TLFormFieldSelect : TLFormFieldSingleLine
12 |
13 | //The list of values to show in the segmented control
14 | @property (nonatomic, strong) NSArray *choicesValues;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldDateTime.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldDateTime.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/12/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldSingleLine.h"
10 |
11 | @interface TLFormFieldDateTime : TLFormFieldSingleLine
12 |
13 | @property (nonatomic, strong) NSString *dateFormat;
14 | @property (nonatomic, assign) UIDatePickerMode pickerMode;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormAllFields.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormAllFields.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/26/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldList.h"
10 | #import "TLFormFieldMultiLine.h"
11 | #import "TLFormFieldSingleLine.h"
12 | #import "TLFormFieldNumeric.h"
13 | #import "TLFormFieldYesNo.h"
14 | #import "TLFormFieldSelect.h"
15 | #import "TLFormFieldTitle.h"
16 | #import "TLFormFieldImage.h"
17 | #import "TLFormFieldDateTime.h"
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldSingleLine.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldSingleLine.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 |
11 |
12 | @interface TLFormFieldSingleLine : TLFormField
13 |
14 | @end
15 |
16 |
17 | @interface TLFormFieldSingleLine (Protected)
18 |
19 | @property (nonatomic, assign) NSString *valueViewText;
20 | @property (nonatomic, readonly) UITextField *textField;
21 |
22 | - (void)setupFieldForEditing;
23 |
24 | @end
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | # We recommend against adding the Pods directory to your .gitignore. However
26 | # you should judge for yourself, the pros and cons are mentioned at:
27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
28 | #
29 | # Note: if you ignore the Pods directory, make sure to uncomment
30 | # `pod install` in .travis.yml
31 | #
32 | # Pods/
33 |
--------------------------------------------------------------------------------
/Pod/Classes/Extensions/NSNumber+NumberType.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSNumber+NumberType.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 |
12 | extern const short kTLNumberBooleanType;
13 | extern const short kTLNumberNanType;
14 |
15 | //This category is used for handling the mapping between NSNumber types (numberWithBool:, numberWithFloat:, etc) and his string values. Ex: you give a "numberWithBool" as value
16 | //then the value is show as "Yes"/"No" and when the value is read you get a "numberWithBool" back. The same works for any type of number thanks to this category.
17 | @interface NSNumber (NumberType)
18 |
19 | - (CFNumberType)numberType;
20 | + (instancetype)numberOfType:(CFNumberType)type withValue:(id)value;
21 |
22 | @end
23 |
24 |
--------------------------------------------------------------------------------
/TLFormView.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint TLFormView.podspec' to ensure this is a
3 | # valid spec and remove all comments before submitting the spec.
4 | #
5 | # Any lines starting with a # are optional, but encouraged
6 | #
7 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
8 | #
9 |
10 | Pod::Spec.new do |s|
11 | s.name = "TLFormView"
12 | s.version = "0.0.5"
13 | s.summary = "An universal iOS form"
14 | s.homepage = "https://github.com/tryolabs/TLFormView"
15 | s.license = 'MIT'
16 | s.author = { "BrunoBerisso" => "bruno@tryolabs.com" }
17 | s.source = { :git => "https://github.com/tryolabs/TLFormView.git", :tag => s.version.to_s }
18 |
19 | s.platform = :ios, '8.0'
20 | s.requires_arc = true
21 |
22 | s.source_files = 'Pod/Classes/**/*'
23 | end
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 BrunoBerisso
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Pod/Classes/Style/TLFormField+UIAppearance.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormField+UIApearance.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/26/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 |
11 |
12 | @interface TLFormField (UIAppearance)
13 |
14 | - (void)setTitleFont:(UIFont *)font UI_APPEARANCE_SELECTOR;
15 | - (void)setTitleTextColor:(UIColor *)color UI_APPEARANCE_SELECTOR;
16 | - (void)setTitleBackgroundColor:(UIColor *)color UI_APPEARANCE_SELECTOR;
17 |
18 | - (void)setValueFont:(UIFont *)font UI_APPEARANCE_SELECTOR;
19 | - (void)setValueTextColor:(UIColor *)color UI_APPEARANCE_SELECTOR;
20 | - (void)setValueBackgroundColor:(UIColor *)color UI_APPEARANCE_SELECTOR;
21 |
22 | - (void)setBorderStyleMask:(TLFormBorderStyleMask)borderMask UI_APPEARANCE_SELECTOR;
23 |
24 |
25 | + (UIButton *) helpButtonAppearance;
26 |
27 | + (UIButton *) addButtonAppearance;
28 |
29 | + (UISegmentedControl *) segmentedAppearance;
30 |
31 | + (UISwitch *) switchAppearance;
32 |
33 | + (UITextField *) textFieldAppearance;
34 |
35 | @end
36 |
37 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormField+Protected.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormField+Protected.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 | #import "TLFormField+UIAppearance.h"
11 |
12 |
13 |
14 | //Private delegate to pass events from the fields to the form
15 |
16 | @protocol TLFormFieldDelegate
17 |
18 | - (void)didSelectField:(TLFormField *)field;
19 | - (void)didChangeValueForField:(TLFormField *)field newValue:(id)value;
20 |
21 | @end
22 |
23 |
24 | extern int const TLFormFieldTitleLabelTag;
25 | extern int const TLFormFieldValueLabelTag;
26 |
27 |
28 | //Forward declaration of some properties and methods used for the subclases
29 |
30 | @interface TLFormField ()
31 |
32 | @property (nonatomic, strong) id defautValue;
33 | @property (nonatomic, weak) id formDelegate;
34 | @property (nonatomic, readonly) NSDictionary *defaultMetrics;
35 | @property (nonatomic, strong) NSString *title;
36 | @property (nonatomic, strong, setter = setValue: , getter = getValue) id fieldValue;
37 |
38 | - (void)setupField:(BOOL)editing;
39 | - (UIView *)titleView;
40 |
41 | @end
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldList.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldList.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 |
11 |
12 | @protocol TLFormFieldListDelegate
13 |
14 | //In edit mode, called when a row is removed from the list. The controler should update the model.
15 | - (void)listFormField:(TLFormField *)field didDeleteRowAtIndexPath:(NSIndexPath *)indexPath;
16 |
17 | @optional
18 |
19 | //In edit mode, called to check if a row could be moved to a different possition in the list.
20 | - (BOOL)listFormField:(TLFormField *)field canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
21 |
22 | //In edit mode, called when a row is moved from when position to another
23 | - (void)listFormField:(TLFormField *)field moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
24 |
25 | //In edit mode, called when the user tap the '+' icon
26 | - (void)listFormFieldAddAction:(TLFormField *)field;
27 |
28 | @end
29 |
30 |
31 | @interface TLFormFieldList : TLFormField
32 |
33 | @property (nonatomic, weak) id delegate;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/Example/TLFormView/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Example/TLFormView/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | com.tryolabs.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Pod/Classes/TLFormModel.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormModel.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "TLFormView.h"
11 |
12 |
13 | @interface TLFormSeparator : NSObject @end
14 | TLFormSeparator * TLFormSeparatorValue ();
15 |
16 |
17 | @interface TLFormText : NSString @end
18 | TLFormText * TLFormTextValue(NSString *text);
19 |
20 | @interface TLFormLongText : NSString @end
21 | TLFormLongText * TLFormLongTextValue(NSString *longText);
22 |
23 | @interface TLFormTitle : NSString @end
24 | TLFormTitle * TLFormTitleValue (NSString *title);
25 |
26 |
27 | @interface TLFormNumber : NSNumber @end
28 | TLFormNumber * TLFormNumberValue (NSNumber *number);
29 |
30 | @interface TLFormBoolean : NSNumber @end
31 | TLFormBoolean * TLFormBooleanValue (BOOL boolean);
32 |
33 | @interface TLFormEnumerated : NSDictionary @end
34 | extern NSString * const TLFormEnumeratedSelectedValue;
35 | extern NSString * const TLFormEnumeratedAllValues;
36 |
37 | TLFormEnumerated * TLFormEnumeratedValue (id current, NSArray *all);
38 |
39 |
40 | @interface TLFormList : NSArray @end
41 | TLFormList * TLFormListValue(NSArray *array);
42 |
43 |
44 | @interface TLFormImage : NSObject @end
45 | TLFormImage * TLFormImageValue (NSObject *urlOrImage);
46 |
47 | @interface TLFormDateTime : NSDate @end
48 | TLFormDateTime * TLFormDateTimeValue (NSDate *date);
49 |
50 | /**
51 | @abstract Model a form using an object.
52 | @discussion This class is intended for subclassing. It infer the implementation of 'TLFormViewDataSource' from his own properties and the 'TLFormViewDelegate' imlementation mantains the property values in synch.
53 | */
54 | @interface TLFormModel : NSObject
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldTitle.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldTitle.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldTitle.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 |
14 | @implementation TLFormFieldTitle {
15 | UILabel *titleLabel;
16 | }
17 |
18 | - (void)setupField:(BOOL)editing {
19 | [super setupField:editing];
20 |
21 | titleLabel = [[UILabel alloc] init];
22 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
23 | titleLabel.textAlignment = NSTextAlignmentCenter;
24 | titleLabel.font = [UIFont boldSystemFontOfSize:18.0];
25 | titleLabel.numberOfLines = 0;
26 |
27 | self.backgroundColor = [UIColor clearColor];
28 | [self addSubview:titleLabel];
29 |
30 | NSDictionary *views = NSDictionaryOfVariableBindings(titleLabel);
31 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[titleLabel]|"
32 | options:0
33 | metrics:self.defaultMetrics
34 | views:views]];
35 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-np-[titleLabel]-np-|"
36 | options:0
37 | metrics:self.defaultMetrics
38 | views:views]];
39 |
40 | [self setValue:self.defautValue];
41 | }
42 |
43 | - (TLFormBorderStyleMask)borderStyle {
44 | return TLFormFieldBorderNone;
45 | }
46 |
47 | - (CGSize)intrinsicContentSize {
48 | return titleLabel.intrinsicContentSize;
49 | }
50 |
51 | - (id)getValue {
52 | return titleLabel.text;
53 | }
54 |
55 | - (void)setValue:(id)fieldValue {
56 | titleLabel.text = fieldValue;
57 | }
58 |
59 | @end
60 |
61 |
--------------------------------------------------------------------------------
/Example/TLFormView/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/23/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19 | // Override point for customization after application launch.
20 | return YES;
21 | }
22 |
23 | - (void)applicationWillResignActive:(UIApplication *)application {
24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
26 | }
27 |
28 | - (void)applicationDidEnterBackground:(UIApplication *)application {
29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
31 | }
32 |
33 | - (void)applicationWillEnterForeground:(UIApplication *)application {
34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
35 | }
36 |
37 | - (void)applicationDidBecomeActive:(UIApplication *)application {
38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
39 | }
40 |
41 | - (void)applicationWillTerminate:(UIApplication *)application {
42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
43 | }
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldNumeric.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldNumeric.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldNumeric.h"
10 | #import "TLFormField+Protected.h"
11 | #import "NSNumber+NumberType.h"
12 |
13 |
14 | @implementation TLFormFieldNumeric{
15 | CFNumberType numberType;
16 | }
17 |
18 | - (void)setupFieldForEditing {
19 | [super setupFieldForEditing];
20 | self.textField.keyboardType = UIKeyboardTypeNumberPad;
21 | }
22 |
23 | //Get and Set value
24 |
25 | - (void)setValue:(id)fieldValue {
26 |
27 | if (!fieldValue)
28 | return;
29 |
30 | if ([fieldValue isKindOfClass:[NSNumber class]]) {
31 | NSNumber *value = (NSNumber *) fieldValue;
32 | numberType = [value numberType];
33 | NSString *stringValue = [value stringValue];
34 |
35 | if (self.textField)
36 | self.textField.text = stringValue;
37 | else
38 | self.valueViewText = stringValue;
39 | } else
40 | [NSException raise:@"Invalid field value" format:@"TLFormFieldNumeric only accept fields of type NSNumber. Suplied value: %@", fieldValue];
41 | }
42 |
43 | - (id)getValue {
44 | NSString *stringValue;
45 |
46 | if (self.textField)
47 | stringValue = self.textField.text;
48 | else
49 | stringValue = self.valueViewText;
50 |
51 | return [NSNumber numberOfType:numberType withValue:stringValue];
52 | }
53 |
54 | //UITextFieldDelegate
55 |
56 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
57 | [self.formDelegate didSelectField:self];
58 | return YES;
59 | }
60 |
61 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
62 |
63 | id newValue = nil;
64 |
65 | if (string.length > 0)
66 | newValue = [textField.text stringByAppendingString:string];
67 | else
68 | newValue = [textField.text substringToIndex:textField.text.length - 1];
69 |
70 | //Translate the value to an NSNumber in the same domain as the one given to the fild as initial value
71 | newValue = [NSNumber numberOfType:numberType withValue:newValue];
72 | [self.formDelegate didChangeValueForField:self newValue:newValue];
73 |
74 | return YES;
75 | }
76 |
77 | @end
78 |
--------------------------------------------------------------------------------
/Pod/Classes/Style/TLFormField+UIAppearance.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormField+UIApearance.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/26/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField+UIAppearance.h"
10 | #import "TLFormField+Protected.h"
11 | #import "TLFormAllFields.h"
12 |
13 |
14 | int const TLFormFieldTitleLabelTag = 42001;
15 | int const TLFormFieldValueLabelTag = 42002;
16 |
17 |
18 | @implementation TLFormField (UIAppearance)
19 |
20 | #pragma mark - Title label accessors
21 |
22 | - (UILabel *)titleLabel {
23 | return (UILabel *) [self viewWithTag:TLFormFieldTitleLabelTag];
24 | }
25 |
26 | - (void)setTitleFont:(UIFont *)titleFont {
27 | [[self titleLabel] setFont:titleFont];
28 | }
29 |
30 | - (void)setTitleTextColor:(UIColor *)color {
31 | [[self titleLabel] setTextColor:color];
32 | }
33 |
34 | - (void)setTitleBackgroundColor:(UIColor *)color {
35 | [[self titleLabel] setBackgroundColor:color];
36 | }
37 |
38 | #pragma mark - Value label accessors
39 |
40 | - (UILabel *)valueLabel {
41 | return (UILabel *) [self viewWithTag:TLFormFieldValueLabelTag];
42 | }
43 |
44 | - (void)setValueFont:(UIFont *)font {
45 | [[self valueLabel] setFont:font];
46 | }
47 |
48 | - (void)setValueTextColor:(UIColor *)color {
49 | [[self valueLabel] setTextColor:color];
50 | }
51 |
52 | - (void)setValueBackgroundColor:(UIColor *)color {
53 | [[self valueLabel] setBackgroundColor:color];
54 | }
55 |
56 | #pragma mark - Border Style
57 |
58 | - (void)setBorderStyleMask:(TLFormBorderStyleMask)borderMask {
59 | self.borderStyle = borderMask;
60 | }
61 |
62 | #pragma mark - Appearance getters
63 |
64 | + (UIButton *)helpButtonAppearance {
65 | return [UIButton appearanceWhenContainedIn:[TLFormField class], nil];
66 | }
67 |
68 | + (UIButton *)addButtonAppearance {
69 | return [UIButton appearanceWhenContainedIn:[TLFormFieldList class], nil];
70 | }
71 |
72 | + (UISegmentedControl *)segmentedAppearance {
73 | return [UISegmentedControl appearanceWhenContainedIn:[TLFormFieldSingleLine class], nil];
74 | }
75 |
76 | + (UISwitch *)switchAppearance {
77 | return [UISwitch appearanceWhenContainedIn:[TLFormFieldSingleLine class], nil];
78 | }
79 |
80 | + (UITextField *)textFieldAppearance {
81 | return [UITextField appearanceWhenContainedIn:[TLFormFieldSingleLine class], nil];
82 | }
83 |
84 | @end
85 |
--------------------------------------------------------------------------------
/Pod/Classes/Extensions/NSNumber+NumberType.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSNumber+NumberType.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "NSNumber+NumberType.h"
10 |
11 |
12 | //Add two specil values that will live side by side to the CFNumberType enum values
13 | const short kTLNumberBooleanType = -1;
14 | const short kTLNumberNanType = -2;
15 |
16 | @implementation NSNumber (NumberType)
17 |
18 | - (CFNumberType)numberType {
19 | //Get if a number is NSNumber-bool value (see: http://stackoverflow.com/questions/2518761/get-type-of-nsnumber )
20 | if (self == (id) kCFBooleanFalse || self == (id) kCFBooleanTrue)
21 | return kTLNumberBooleanType;
22 | else
23 | return CFNumberGetType((CFNumberRef)self);
24 | }
25 |
26 | //Given a type and a value return the corresponding NSNumber. This is like use NSNumberFormatter but much better.
27 | + (instancetype)numberOfType:(CFNumberType)type withValue:(id)value {
28 |
29 | const void *numberValue = NULL;
30 |
31 | switch (type) {
32 | case kCFNumberCharType:
33 | case kCFNumberSInt8Type: {
34 | char tmp = [value charValue];
35 | numberValue = &tmp;
36 | break;
37 | }
38 | case kCFNumberShortType:
39 | case kCFNumberSInt16Type: {
40 | short tmp = [value shortValue];
41 | numberValue = &tmp;
42 | break;
43 | }
44 | case kCFNumberIntType:
45 | case kCFNumberSInt32Type: {
46 | int tmp = [value intValue];
47 | numberValue = &tmp;
48 | break;
49 | }
50 | case kCFNumberLongType: {
51 | long tmp = [value longValue];
52 | numberValue = &tmp;
53 | break;
54 | }
55 | case kCFNumberLongLongType:
56 | case kCFNumberSInt64Type: {
57 | long long tmp = [value longLongValue];
58 | numberValue = &tmp;
59 | break;
60 | }
61 | case kCFNumberCGFloatType:
62 | case kCFNumberFloatType:
63 | case kCFNumberFloat32Type: {
64 | float tmp = [value floatValue];
65 | numberValue = &tmp;
66 | break;
67 | }
68 | case kCFNumberDoubleType:
69 | case kCFNumberFloat64Type: {
70 | double tmp = [value doubleValue];
71 | numberValue = &tmp;
72 | break;
73 | }
74 | case kCFNumberNSIntegerType: {
75 | NSInteger tmp = [value integerValue];
76 | numberValue = &tmp;
77 | break;
78 | }
79 | default:
80 | numberValue = NULL;
81 | break;
82 | }
83 |
84 | return CFBridgingRelease(CFNumberCreate(kCFAllocatorDefault, type, numberValue));
85 | }
86 |
87 | @end
88 |
89 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormField.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormField.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 |
12 | typedef enum {
13 | TLFormFieldBorderNone = 0,
14 | TLFormFieldBorderTop = 1 << 0,
15 | TLFormFieldBorderRight = 1 << 1,
16 | TLFormFieldBorderBottom = 1 << 2,
17 | TLFormFieldBorderLeft = 1 << 3,
18 | TLFormFieldBorderAll = 255
19 | } TLFormFieldBorder;
20 |
21 | typedef char TLFormBorderStyleMask;
22 |
23 |
24 | //Uncoment this to color all the subviews to chack any posible layout issues
25 | //#define TLFormViewLayoutDebug
26 |
27 |
28 | /**
29 | @abstract Represent a filed in that will be show in a TLFormView
30 | @discussion This is the base class for all the fields that can be used in the TLFormView.
31 | */
32 | @interface TLFormField : UIView
33 |
34 | ///The field name is used to identify a field in the form. Is never showed to the user.
35 | @property (nonatomic, strong) NSString *fieldName;
36 |
37 | ///If not empty show a quesion mark button next to the field title when the form is on edit mode that when taped show a popver with this text
38 | @property (nonatomic, strong) NSString *helpText;
39 |
40 | ///Mask that tells which borders should be draw
41 | @property (nonatomic, assign) TLFormBorderStyleMask borderStyle;
42 |
43 | /**
44 | @abstract Predicate used to determine the visibility of this field.
45 | @discussion If this predicate evaluates to TRUE the field es visible. The predicate is evaluated every time a field value change, by an user interaction or by calling 'refreshValues'. In the predicate context, SELF is the TLFormField object that holds the predicate and all the other fields are accesible as variables.
46 |
47 | Example: field.visibilityPredicate = [NSPredicate predicateWithFormat:@"$ingredients.value[SIZE] > 4"];
48 |
49 | Keep in mind that the type of 'value' may change depending the TLFormFieldType and TLFormFieldInputType of the field being referenced in the expresion.
50 |
51 | IMPORTANT: to hide the fileds the form create an NSLayoutConstraint for the field height and set it to 0.0. If some other field in the layout definition is taied to this field height in any way it will also be affected. Also because this add a new constraint it can cause an inconsistenci in the layout system and throw an exception. (Don't panic, look here: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutolayoutPG.pdf - "Auto Layout Degrades Gracefully with Unsatisfiable Constraints"
52 | */
53 | @property (nonatomic, strong) NSPredicate *visibilityPredicate;
54 |
55 | /**
56 | @abstract Construct a field with the given parameters.
57 | @discussion Designated contrstructor for all the TLFormFields.
58 | @param fieldName The name of the fields
59 | @param displayName The title of the field
60 | @param defaultValue The value to use as default if there is no value returned form the data source
61 | @return A new instance of TLFormField
62 | */
63 | + (instancetype)formFieldWithName:(NSString *)fieldName title:(NSString *)displayName andDefaultValue:(id)defaultValue;
64 |
65 | @end
66 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldYesNo.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldYesNo.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldYesNo.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 | @implementation TLFormFieldYesNo {
14 | UISwitch *yesNoSelect;
15 | }
16 |
17 | - (void)setupFieldForEditing {
18 |
19 | yesNoSelect = [[UISwitch alloc] init];
20 | yesNoSelect.tag = TLFormFieldValueLabelTag;
21 | yesNoSelect.translatesAutoresizingMaskIntoConstraints = NO;
22 | [yesNoSelect addTarget:self action:@selector(controlValueChange) forControlEvents:UIControlEventValueChanged];
23 |
24 | [self addSubview:yesNoSelect];
25 |
26 | UIView *titleView = [self titleView];
27 | NSDictionary *views = NSDictionaryOfVariableBindings(titleView, yesNoSelect);
28 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-bp-[yesNoSelect]-bp-|"
29 | options:NSLayoutFormatAlignAllCenterY
30 | metrics:self.defaultMetrics
31 | views:views]];
32 |
33 | //The vertical constraints needs to be set with explicit contraints because the visual format language can't express this rules.
34 | [self addConstraint:[NSLayoutConstraint constraintWithItem:yesNoSelect
35 | attribute:NSLayoutAttributeCenterY
36 | relatedBy:NSLayoutRelationEqual
37 | toItem:self
38 | attribute:NSLayoutAttributeCenterY
39 | multiplier:1.0 constant:0.0]];
40 | [self addConstraint:[NSLayoutConstraint constraintWithItem:titleView
41 | attribute:NSLayoutAttributeCenterY
42 | relatedBy:NSLayoutRelationEqual
43 | toItem:self
44 | attribute:NSLayoutAttributeCenterY
45 | multiplier:1.0 constant:0.0]];
46 | }
47 |
48 | //Get and Set value
49 |
50 | - (void)setValue:(id)fieldValue {
51 |
52 | if (!fieldValue)
53 | return;
54 |
55 | if ([fieldValue isKindOfClass:[NSNumber class]]) {
56 | NSNumber *value = (NSNumber *) fieldValue;
57 |
58 | if (yesNoSelect)
59 | yesNoSelect.on = [value boolValue];
60 | else
61 | self.valueViewText = [value boolValue] ? @"Yes" : @"No";
62 |
63 | } else
64 | [NSException raise:@"Invalid field value" format:@"TLFormFieldYesNo only accept fields of type NSNumber (boolean). Suplied value: %@", fieldValue];
65 | }
66 |
67 | - (id)getValue {
68 | if (yesNoSelect)
69 | return @(yesNoSelect.on);
70 | else
71 | return @([self.valueViewText boolValue]);
72 | }
73 |
74 | //UISwitch value change
75 |
76 | - (void)controlValueChange {
77 | [self.formDelegate didChangeValueForField:self newValue:[self getValue]];
78 | }
79 |
80 | @end
81 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldSelect.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldSelect.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/13/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldSelect.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 |
14 | @implementation TLFormFieldSelect {
15 | UISegmentedControl *segmented;
16 | }
17 |
18 | - (void)setupFieldForEditing {
19 |
20 | segmented = [[UISegmentedControl alloc] init];
21 | segmented.tag = TLFormFieldValueLabelTag;
22 | segmented.translatesAutoresizingMaskIntoConstraints = NO;
23 | [segmented addTarget:self action:@selector(controlValueChange) forControlEvents:UIControlEventValueChanged];
24 |
25 | for (NSString *choice in self.choicesValues)
26 | [segmented insertSegmentWithTitle:choice atIndex:[self.choicesValues indexOfObject:choice] animated:NO];
27 |
28 | [self addSubview:segmented];
29 |
30 | UIView *titleView = [self titleView];
31 | NSDictionary *views = NSDictionaryOfVariableBindings(titleView, segmented);
32 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-sp-[titleView]-sp-[segmented]-np-|"
33 | options:NSLayoutFormatAlignAllCenterX
34 | metrics:self.defaultMetrics
35 | views:views]];
36 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-bp-|"
37 | options:NSLayoutFormatAlignAllCenterY
38 | metrics:self.defaultMetrics
39 | views:views]];
40 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[segmented]-bp-|"
41 | options:NSLayoutFormatAlignAllCenterY
42 | metrics:self.defaultMetrics
43 | views:views]];
44 | }
45 |
46 | //Get and Set value
47 |
48 | - (void)setValue:(id)fieldValue {
49 |
50 | if (!fieldValue)
51 | return;
52 |
53 | if ([fieldValue isKindOfClass:[NSString class]]) {
54 | NSString *stringValue = (NSString *) fieldValue;
55 |
56 | if (segmented) {
57 | for (int i = 0; i < [segmented numberOfSegments]; i++) {
58 | if ([stringValue isEqualToString:[segmented titleForSegmentAtIndex:i]]) {
59 | segmented.selectedSegmentIndex = i;
60 | break;
61 | }
62 | }
63 | } else
64 | self.valueViewText = stringValue;
65 |
66 | } else
67 | [NSException raise:@"Invalid field value" format:@"TLFormFieldSelect only accept fields of type NSString. Suplied value: %@", fieldValue];
68 | }
69 |
70 | - (id)getValue {
71 | if (segmented)
72 | return [segmented titleForSegmentAtIndex:segmented.selectedSegmentIndex];
73 | else
74 | return self.valueViewText;
75 | }
76 |
77 | //UISegmented value change
78 |
79 | - (void)controlValueChange {
80 | [self.formDelegate didChangeValueForField:self newValue:[self getValue]];
81 | }
82 |
83 | @end
84 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldDateTime.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldDateTime.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 5/12/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldDateTime.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 |
14 | @implementation TLFormFieldDateTime {
15 | dispatch_once_t _onceTokenFormatter;
16 | NSDateFormatter *_formatter;
17 | UIDatePicker *picker;
18 | }
19 |
20 | - (instancetype)initWithFrame:(CGRect)frame {
21 | if (self = [super initWithFrame:frame]) {
22 | self.dateFormat = @"MMM dd, yyyy HH:mm";
23 | self.pickerMode = UIDatePickerModeDateAndTime;
24 | }
25 | return self;
26 | }
27 |
28 | - (NSDateFormatter *)formatter {
29 | dispatch_once(&_onceTokenFormatter, ^{
30 | _formatter = [[NSDateFormatter alloc] init];
31 | _formatter.dateFormat = self.dateFormat;
32 | });
33 | return _formatter;
34 | }
35 |
36 | - (void)setupFieldForEditing {
37 | picker = [[UIDatePicker alloc] init];
38 | picker.datePickerMode = self.pickerMode;
39 | picker.translatesAutoresizingMaskIntoConstraints = NO;
40 | [picker addTarget:self action:@selector(controlValueChange) forControlEvents:UIControlEventValueChanged];
41 | [self addSubview:picker];
42 | self.clipsToBounds = YES;
43 |
44 | UIView *titleView = [self titleView];
45 | NSDictionary *views = NSDictionaryOfVariableBindings(picker, titleView);
46 |
47 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-np-[titleView][picker]-np-|"
48 | options:0
49 | metrics:self.defaultMetrics
50 | views:views]];
51 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-bp-[titleView]-bp-|"
52 | options:0
53 | metrics:self.defaultMetrics
54 | views:views]];
55 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-np-[picker]-np-|"
56 | options:0
57 | metrics:self.defaultMetrics
58 | views:views]];
59 | }
60 |
61 | //Get and Set value
62 |
63 | - (void)setValue:(id)fieldValue {
64 |
65 | if (!fieldValue)
66 | return;
67 |
68 | if ([fieldValue isKindOfClass:[NSDate class]]) {
69 |
70 | if (picker)
71 | picker.date = fieldValue;
72 | else
73 | self.valueViewText = [[self formatter] stringFromDate:fieldValue];
74 |
75 | } else
76 | [NSException raise:@"Invalid field value" format:@"TLFormFieldNumeric only accept fields of type NSNumber. Suplied value: %@", fieldValue];
77 | }
78 |
79 | - (id)getValue {
80 | if (picker)
81 | return picker.date;
82 | else
83 | return [[self formatter] dateFromString:self.valueViewText];
84 | }
85 |
86 | //UIDatePicker value change
87 |
88 | - (void)controlValueChange {
89 | [self.formDelegate didChangeValueForField:self newValue:[self getValue]];
90 | }
91 |
92 | @end
93 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldMultiLine.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldMultiLine.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldMultiLine.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 |
14 | @interface TLFormFieldMultiLine ()
15 |
16 | @end
17 |
18 |
19 | @implementation TLFormFieldMultiLine {
20 | UITextView *textView;
21 | }
22 |
23 | - (void)setupField:(BOOL)editing {
24 | [super setupField:editing];
25 |
26 | UIView *titleView = [self titleView];
27 | [self addSubview:titleView];
28 |
29 | if (editing) {
30 | UILabel *titleLabel = (UILabel *) [titleView viewWithTag:TLFormFieldTitleLabelTag];
31 | titleLabel.textColor = [UIColor grayColor];
32 | }
33 |
34 | textView = [[UITextView alloc] init];
35 | textView.font = [UIFont systemFontOfSize:12];
36 | textView.scrollEnabled = NO;
37 | textView.editable = editing;
38 | textView.translatesAutoresizingMaskIntoConstraints = NO;
39 | textView.delegate = self;
40 | [self addSubview:textView];
41 |
42 | NSDictionary *views = NSDictionaryOfVariableBindings(titleView, textView);
43 |
44 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-bp-|"
45 | options:0
46 | metrics:self.defaultMetrics
47 | views:views]];
48 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-np-[textView]-np-|"
49 | options:0
50 | metrics:self.defaultMetrics
51 | views:views]];
52 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-np-[titleView][textView]-sp-|"
53 | options:0
54 | metrics:self.defaultMetrics
55 | views:views]];
56 |
57 | [self setValue:self.defautValue];
58 | }
59 |
60 | - (CGSize)intrinsicContentSize {
61 | CGFloat width = 0.0, height = 0.0;
62 |
63 | for (UIView *subview in self.subviews) {
64 | width += subview.intrinsicContentSize.width;
65 | height += subview.intrinsicContentSize.height;
66 | }
67 |
68 | return CGSizeMake(width, height);
69 | }
70 |
71 | //Get and Set value
72 |
73 | - (void)setValue:(id)fieldValue {
74 | textView.text = fieldValue;
75 | }
76 |
77 | - (id)getValue {
78 | return textView.text;
79 | }
80 |
81 | //UITextFieldDelegate
82 |
83 | - (void)textViewDidBeginEditing:(UITextView *)_textView {
84 | [self.formDelegate didSelectField:self];
85 | }
86 |
87 | - (BOOL)textView:(UITextView *)_textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
88 |
89 | NSString *newValue = nil;
90 | if (text.length > 0)
91 | newValue = [_textView.text stringByAppendingString:text];
92 | else
93 | newValue = [_textView.text substringToIndex:_textView.text.length - 1];
94 |
95 | [self.formDelegate didChangeValueForField:self newValue:newValue];
96 | return YES;
97 | }
98 |
99 | @end
100 |
--------------------------------------------------------------------------------
/Example/TLFormView/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Example/TLFormView/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldSingleLine.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldSingleLine.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldSingleLine.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 | @interface TLFormFieldSingleLine ()
14 | @end
15 |
16 |
17 | @implementation TLFormFieldSingleLine {
18 | UITextField *textField;
19 | }
20 |
21 | - (void)setupField:(BOOL)editing {
22 | [super setupField:editing];
23 |
24 | UIView *titleView = [self titleView];
25 | [self addSubview:titleView];
26 |
27 | if (editing) {
28 |
29 | UILabel *titleLabel = (UILabel *) [titleView viewWithTag:TLFormFieldTitleLabelTag];
30 | titleLabel.textColor = [UIColor grayColor];
31 | [self setupFieldForEditing];
32 |
33 | } else {
34 |
35 | UILabel *valueLabel = [[UILabel alloc] init];
36 | valueLabel.tag = TLFormFieldValueLabelTag;
37 | valueLabel.numberOfLines = 1;
38 | valueLabel.textAlignment = NSTextAlignmentRight;
39 | valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
40 |
41 | [self addSubview:valueLabel];
42 |
43 | //Adjust the compression resistance for each view so the labels resize always in the same way when the size of the container change. Without this set explicitly
44 | //the behavior is inconsistent
45 | [titleView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
46 | [valueLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
47 |
48 | NSDictionary *views = NSDictionaryOfVariableBindings(titleView, valueLabel);
49 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-bp-[valueLabel]-bp-|"
50 | options:NSLayoutFormatAlignAllCenterY
51 | metrics:self.defaultMetrics
52 | views:views]];
53 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-sp-[titleView]-sp-|"
54 | options:NSLayoutFormatAlignAllCenterY
55 | metrics:self.defaultMetrics
56 | views:views]];
57 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-sp-[valueLabel]-sp-|"
58 | options:NSLayoutFormatAlignAllCenterY
59 | metrics:self.defaultMetrics
60 | views:views]];
61 | }
62 |
63 | [self setValue:self.defautValue];
64 | }
65 |
66 | - (CGSize)intrinsicContentSize {
67 | CGFloat width = 0.0, height = 0.0;
68 |
69 | for (UIView *subview in self.subviews) {
70 | width += subview.intrinsicContentSize.width;
71 | //Don't acumulte the height, use the maximum
72 | height = MAX(height, subview.intrinsicContentSize.height);
73 | }
74 |
75 | return CGSizeMake(width, height);
76 | }
77 |
78 | //Get and Set value
79 |
80 | - (void)setValue:(id)fieldValue {
81 |
82 | if (!fieldValue)
83 | return;
84 |
85 | NSString *stringValue;
86 | if ([fieldValue isKindOfClass:[NSString class]] == NO)
87 | stringValue = [fieldValue stringValue];
88 | else
89 | stringValue = fieldValue;
90 |
91 | if (textField)
92 | textField.text = stringValue;
93 | else
94 | self.valueViewText = stringValue;
95 | }
96 |
97 | - (id)getValue {
98 | if (textField)
99 | return textField.text;
100 | else
101 | return self.valueViewText;
102 | }
103 |
104 | //UITextFieldDelegate
105 |
106 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
107 | [self.formDelegate didSelectField:self];
108 | return YES;
109 | }
110 |
111 | - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
112 |
113 | id newValue = nil;
114 |
115 | if (string.length > 0)
116 | newValue = [_textField.text stringByAppendingString:string];
117 | else
118 | newValue = [_textField.text substringToIndex:_textField.text.length - 1];
119 |
120 | [self.formDelegate didChangeValueForField:self newValue:newValue];
121 |
122 | return YES;
123 | }
124 |
125 | @end
126 |
127 |
128 | @implementation TLFormFieldSingleLine (Protected)
129 |
130 | - (NSString *)valueViewText {
131 | return [[self viewWithTag:TLFormFieldValueLabelTag] performSelector:@selector(text)];
132 | }
133 |
134 | - (void)setValueViewText:(NSString *)valueViewText {
135 | [[self viewWithTag:TLFormFieldValueLabelTag] performSelector:@selector(setText:) withObject:valueViewText];
136 | }
137 |
138 | - (UITextField *)textField {
139 | return textField;
140 | }
141 |
142 | - (void)setupFieldForEditing {
143 |
144 | textField = [[UITextField alloc] init];
145 | textField.textAlignment = NSTextAlignmentRight;
146 | textField.translatesAutoresizingMaskIntoConstraints = NO;
147 | textField.borderStyle = UITextBorderStyleNone;
148 | textField.delegate = self;
149 | [self addSubview:textField];
150 |
151 | UIView *titleView = [self titleView];
152 | NSDictionary *views = NSDictionaryOfVariableBindings(titleView, textField);
153 |
154 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-sp-[titleView]-sp-|"
155 | options:NSLayoutFormatAlignAllCenterX
156 | metrics:self.defaultMetrics
157 | views:views]];
158 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-np-[textField]-bp-|"
159 | options:NSLayoutFormatAlignAllCenterY
160 | metrics:self.defaultMetrics
161 | views:views]];
162 | }
163 |
164 | @end
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldList.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldList.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldList.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 | #define kTLFormFieldListRowHeight 44.0
14 |
15 |
16 | @interface TLFormFieldList ()
17 |
18 | @end
19 |
20 |
21 | @implementation TLFormFieldList {
22 | UILabel *titleLabel;
23 | UITableView *tableView;
24 | UIButton *plusButton;
25 | NSMutableArray *items;
26 | }
27 |
28 | - (BOOL)delegateCanPerformSelector:(SEL)selector {
29 | return self.delegate && [self.delegate respondsToSelector:selector];
30 | }
31 |
32 | - (void)setupField:(BOOL)editing {
33 | [super setupField:editing];
34 |
35 | UIView *titleView = [self titleView];
36 | [self addSubview:titleView];
37 |
38 | titleLabel = (UILabel *) [self viewWithTag:TLFormFieldTitleLabelTag];
39 | if (editing)
40 | titleLabel.textColor = [UIColor grayColor];
41 |
42 | tableView = [[UITableView alloc] init];
43 | tableView.translatesAutoresizingMaskIntoConstraints = NO;
44 | tableView.scrollEnabled = NO;
45 | tableView.dataSource = self;
46 | tableView.editing = editing;
47 | tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
48 | [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"ItemCell"];
49 | [self addSubview:tableView];
50 |
51 | items = [self.defautValue mutableCopy];
52 |
53 | NSDictionary *views;
54 |
55 | if (editing) {
56 | plusButton = [[UIButton alloc] init];
57 | plusButton.translatesAutoresizingMaskIntoConstraints = NO;
58 | plusButton.titleLabel.font = [UIFont systemFontOfSize:35];
59 | [plusButton setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
60 | [plusButton setTitle:@"+" forState:UIControlStateNormal];
61 | [plusButton addTarget:self action:@selector(plusAction:) forControlEvents:UIControlEventTouchUpInside];
62 |
63 | [self addSubview:plusButton];
64 |
65 | views = NSDictionaryOfVariableBindings(titleView, tableView, plusButton);
66 |
67 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-[plusButton]-bp-|"
68 | options:0
69 | metrics:self.defaultMetrics
70 | views:views]];
71 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-np-[tableView]-np-|"
72 | options:0
73 | metrics:self.defaultMetrics
74 | views:views]];
75 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[plusButton][tableView]-sp-|"
76 | options:0
77 | metrics:self.defaultMetrics
78 | views:views]];
79 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[titleView][tableView]-sp-|"
80 | options:0
81 | metrics:self.defaultMetrics
82 | views:views]];
83 |
84 | //Set the compresson and hugging priorities for the title and the button so it behave correctly with long text
85 | [titleView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
86 | [plusButton setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
87 |
88 | } else {
89 | views = NSDictionaryOfVariableBindings(titleView, tableView);
90 |
91 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-bp-[titleView]-bp-|"
92 | options:0
93 | metrics:self.defaultMetrics
94 | views:views]];
95 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-np-[tableView]-np-|"
96 | options:0
97 | metrics:self.defaultMetrics
98 | views:views]];
99 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-np-[titleView][tableView]-np-|"
100 | options:0
101 | metrics:self.defaultMetrics
102 | views:views]];
103 | }
104 | }
105 |
106 | - (void)setValue:(id)fieldValue {
107 | if (items.count != [fieldValue count])
108 | [self invalidateIntrinsicContentSize];
109 |
110 | items = [fieldValue mutableCopy];
111 | [tableView reloadData];
112 | }
113 |
114 | - (id)getValue {
115 | return items;
116 | }
117 |
118 | - (CGSize)intrinsicContentSize {
119 | CGFloat width = 0.0, height = 0.0;
120 |
121 | width = titleLabel.intrinsicContentSize.width + plusButton.intrinsicContentSize.width;
122 | height = MAX(titleLabel.intrinsicContentSize.height, plusButton.intrinsicContentSize.height) + (items.count * kTLFormFieldListRowHeight);
123 |
124 | return CGSizeMake(width, height);
125 | }
126 |
127 | - (void)plusAction:(id)sender {
128 | if ([self delegateCanPerformSelector:@selector(listFormFieldAddAction:)])
129 | [self.delegate listFormFieldAddAction:self];
130 | }
131 |
132 | #pragma mark - UITableViewDelegate
133 |
134 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
135 | return kTLFormFieldListRowHeight;
136 | }
137 |
138 | #pragma mark - UITableViewDataSource
139 |
140 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
141 | return items.count;
142 | }
143 |
144 | - (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
145 | UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:@"ItemCell"];
146 | cell.textLabel.font = [UIFont systemFontOfSize:12];
147 | cell.selectionStyle = UITableViewCellSelectionStyleNone;
148 | cell.textLabel.text = items[indexPath.row];
149 | return cell;
150 | }
151 |
152 | - (void)tableView:(UITableView *)_tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
153 | [items removeObjectAtIndex:indexPath.row];
154 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
155 |
156 | //Invalidate the intrinsic content size so the layout is recalculated. The delay is used to let the animation end before update the layout
157 | [self performSelector:@selector(invalidateIntrinsicContentSize) withObject:nil afterDelay:0.3];
158 |
159 | [self.delegate listFormField:self didDeleteRowAtIndexPath:indexPath];
160 | }
161 |
162 | - (BOOL)tableView:(UITableView *)_tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
163 | return tableView.editing;
164 | }
165 |
166 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
167 | //If this method is not implemented on the delegate return YES
168 | return ![self delegateCanPerformSelector:@selector(listFormField:canMoveRowAtIndexPath:)] || [self.delegate listFormField:self canMoveRowAtIndexPath:indexPath];
169 | }
170 |
171 | - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
172 | [self.delegate listFormField:self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
173 | }
174 |
175 | @end
176 |
--------------------------------------------------------------------------------
/Pod/Classes/TLFormView.h:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormView.h
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 3/19/14.
6 | // Copyright (c) 2014 Gathered Table LLC. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "TLFormField.h"
11 |
12 |
13 | /************************************************************************************************************************************************/
14 | /******************************************************* TLFormView Delegate and Data Source **************************************************/
15 | /************************************************************************************************************************************************/
16 |
17 | /**
18 | @abstract Basic interaction with the fields
19 | @discussion This delegate is fired when the user interact with any of the fields.
20 | */
21 | @class TLFormView;
22 | @protocol TLFormViewDelegate
23 |
24 | @optional
25 |
26 | /**
27 | @abstract Called when the field is selected.
28 | @discussion When this method is fired depends of the TLFormField implementation that handle the field. In the mayority of the cases it's obvious when a field is selected (ex: a UISegmentedControl in a field)
29 | @param form The TLFormView instance being setup
30 | @param field The TLFormField instance that fire the event
31 | */
32 | - (void)formView:(TLFormView *)form didSelectField:(TLFormField *)field;
33 |
34 | /**
35 | @abstract In edit mode, called when the user change the value of some field.
36 | @discussion When this method is fired depends of the TLFormField implementation that handle the field. In the mayority of the cases it's obvious when a value change (ex: a UISegmentedControl in a field)
37 | @param form The TLFormView instance being setup
38 | @param field The TLFormField instance that fire the event
39 | @param value The new value of the field
40 | */
41 | - (void)formView:(TLFormView *)form didChangeValueForField:(TLFormField *)field newValue:(id)value;
42 |
43 | @end
44 |
45 |
46 | /**
47 | @abstract Data source to describe the setup process of the form.
48 | @discussion This protocol delegates the setup process of the TLFormView to another object.
49 | */
50 | @protocol TLFormViewDataSource
51 |
52 | @optional
53 |
54 | /**
55 | @abstract Return the actual values to show in each field
56 | @discussion If this method is not implemented the values are allways the ones set as default. Keep in mind that if this method is omited the calls to 'refreshValues' made to TLFromView will have no effect
57 | @param form The TLFormView instance being setup
58 | @param fieldName The name of the field
59 | @return The value corresponding the field name
60 | */
61 | - (id)formView:(TLFormView *)form valueForFieldWithName:(NSString *)fieldName;
62 |
63 | @required
64 |
65 | /**
66 | @abstract Return an array of NSStrings corresponding to the 'fieldName' of the TLFormField.
67 | @param form The TLFormView instance being setup
68 | @return An NSArray of field names
69 | */
70 | - (NSArray *)fieldNamesToShowInFormView:(TLFormView *)form;
71 |
72 | /**
73 | @abstract A TLFormField for a given field name
74 | @discussion Return a TLFormField sublass instance for a given field name.
75 | @param form The TLFormView instance being setup
76 | @param fieldName The name of the field
77 | @return The TLFormField object for a given 'fieldName'
78 | */
79 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName;
80 |
81 | //Return an array of NSStrings. Each string is a constraint to be applied to the form using the to 'constraintsWithVisualFormat:' method of NSLayoutConstrin with the only
82 | //difference that instead of use the view names it should have the field names. e.g:
83 | //
84 | // TLFormField *userNameField = [TLFormField formFieldWithType:TLFormFieldTypeSingleLine name:@"userName" displayName:@"User Name" andDefaultValue:nil];
85 | //
86 | // [form addConstraintsWithFormat:@[@"V:|-padding-[userName]-padding-|"]];
87 | //
88 | //Because the TLFormField is a subclass of UIScrollView is important to note that the constraints must be absolute in therms of size. For more details
89 | //see the 'addConstraintsWithFormat:' in TLFormView and check the documentation at
90 | //https://developer.apple.com/library/ios/documentation/userexperience/conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html#//apple_ref/doc/uid/TP40010853-CH5-SW2
91 | - (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form;
92 |
93 | @end
94 |
95 | /************************************************************************************************************************************************/
96 | /************************************************************** TLFormViewModel ***************************************************************/
97 | /************************************************************************************************************************************************/
98 |
99 | /**
100 | @abstract The form model interface
101 | @discussion Describe the iterface that an object must implement to be set as a form model with 'setFormModel:' method of TLFormView.
102 | */
103 | @protocol TLFormViewModel
104 | @end
105 |
106 | /************************************************************************************************************************************************/
107 | /**************************************************************** TLFormView ******************************************************************/
108 | /************************************************************************************************************************************************/
109 |
110 |
111 | /**
112 | @abstract The form view
113 | @discussion The form view is a subclass of UIScrollView that position his subviews using autolayout constraints. The constraints must be provided by the user of the view. The TLFormView provide means of interaction with his fields throuht a delegate.
114 | The setup is performed by calling the 'setupFields' method or in the first call to 'layoutSubviews'. Multiple calls to this method are allowed, each call remove all the previous setup and start over. The values in the form can be refreshed using the 'refreshValues' method and can be read using the 'valuesForFields' method that returns a NSDictionary with the 'fieldName' property as key.
115 | */
116 | @interface TLFormView : UIScrollView
117 |
118 | ///This is the default margin used in the format strings of that define the layout. If changed after the layout was applied has no efect.
119 | @property (nonatomic, assign) CGFloat margin;
120 |
121 | ////Set the editing state on all the fields.
122 | @property (nonatomic, assign) BOOL editing;
123 |
124 | ////Delegate that recieve a message every time the user interact with a field that's report interaction
125 | @property (nonatomic, weak) IBOutlet id formDelegate;
126 |
127 | ////Data source
128 | @property (nonatomic, weak) IBOutlet id formDataSource;
129 |
130 | ///Header to show in the form
131 | @property (nonatomic, weak) IBOutlet UIView *header;
132 | ///Footer to show in the form
133 | @property (nonatomic, weak) IBOutlet UIView *footer;
134 |
135 | /**
136 | @abstract Set the form model
137 | @discussion Set an object to act as the form model. A form model is an object that will be set as the *unique* data source and a *shadow* delegate.
138 |
139 | Being a *unique* data source means that when this mehod is call any previous object set as data source will be forgot and any try to set it affter will throw an exception.
140 |
141 | A *shadow* delegate means that other delegate can be set and the messages will be send to both objects, the form model and any other object.
142 |
143 | @param model An object conforming to the TLFormViewModel protocol
144 | */
145 | - (void)setFormModel:(id )model;
146 |
147 | /**
148 | @abstract Setup the form.
149 | @discussion Make the appropiet calls to the 'formDataSource' and construct the form.
150 | */
151 | - (void)setupFields;
152 |
153 | /**
154 | @abstract Read the values for all the fields in the form.
155 | @discussion The returned dicionary has the 'fieldName' property of the fields as key and a value of the coresponding type depending on the field type set when created.
156 | @return A NSDictionary containing all the values of the form.
157 | */
158 | - (NSDictionary *)valuesForFields;
159 |
160 | /**
161 | @abstract Refresh the values in the fields.
162 | @discussion Get the values for each field sending a 'formView:valueForFieldWithName:' to the data source to get the new values and set it on each field.
163 | */
164 | - (void)reloadValues;
165 |
166 | @end
167 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormField.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormField.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormField.h"
10 | #import "TLFormField+Protected.h"
11 | #import "TLFormAllFields.h"
12 |
13 |
14 | @implementation UIPopoverController (iPhoneSupport)
15 |
16 | + (BOOL)_popoversDisabled {
17 | return NO;
18 | }
19 |
20 | @end
21 |
22 |
23 |
24 | #pragma mark - HelpTooltipPopoverControler
25 | /************************************************** HelpTooltipPopoverControler ***************************************************************/
26 | //This class is the content of the help tooltip. It's only a UITextView that display the value of 'helpText' property
27 |
28 | @interface HelpTooltipPopoverControler : UIViewController
29 | + (id)helpTooltipControllerWithText:(NSString *)helpText;
30 | @end
31 |
32 | @implementation HelpTooltipPopoverControler {
33 | NSString *text;
34 | UITextView *textView;
35 | }
36 |
37 | + (id)helpTooltipControllerWithText:(NSString *)helpText {
38 | HelpTooltipPopoverControler *controller = [[HelpTooltipPopoverControler alloc] init];
39 | controller->text = helpText;
40 | controller.modalPresentationStyle = UIModalPresentationPopover;
41 | controller.popoverPresentationController.delegate = controller;
42 | return controller;
43 | }
44 |
45 | - (void)viewDidLoad {
46 | [super viewDidLoad];
47 |
48 | textView = [[UITextView alloc] init];
49 | textView.translatesAutoresizingMaskIntoConstraints = NO;
50 | textView.font = [UIFont systemFontOfSize:13];
51 | textView.scrollEnabled = NO;
52 | textView.editable = NO;
53 | textView.selectable = NO;
54 | textView.text = text;
55 | [self.view addSubview:textView];
56 |
57 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[textView]|"
58 | options:NSLayoutFormatAlignAllCenterY
59 | metrics:0
60 | views:NSDictionaryOfVariableBindings(textView)]];
61 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[textView]|"
62 | options:NSLayoutFormatAlignAllCenterX
63 | metrics:0
64 | views:NSDictionaryOfVariableBindings(textView)]];
65 | }
66 |
67 | - (CGSize)preferredContentSize {
68 | BOOL is_iPhone = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone;
69 | return [textView sizeThatFits:CGSizeMake(is_iPhone ? 200 : 250, INFINITY)];
70 | }
71 |
72 | - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
73 | return UIModalPresentationNone;
74 | }
75 |
76 | @end
77 |
78 |
79 | #pragma mark - TLFormField
80 | //The base class for the form fields
81 |
82 | @implementation TLFormField {
83 | UIView *_titleView;
84 | NSLayoutConstraint *hiddenConstraint;
85 | UIPopoverController *popover;
86 | }
87 |
88 | #pragma mark - Field Setup
89 |
90 | + (instancetype)formFieldWithName:(NSString *)fieldName title:(NSString *)title andDefaultValue:(id)defaultValue {
91 | return [[self alloc] initWithName:fieldName title:title andDefaultValue:defaultValue];
92 | }
93 |
94 | - (instancetype)initWithName:(NSString *)fieldName title:(NSString *)title andDefaultValue:(id)defaultValue {
95 | self = [super init];
96 |
97 | self.borderStyle = TLFormFieldBorderNone;
98 |
99 | if (self) {
100 | self.fieldName = fieldName;
101 | self.defautValue = defaultValue;
102 | self.title = title;
103 | self.translatesAutoresizingMaskIntoConstraints = NO;
104 | self.backgroundColor = [UIColor whiteColor];
105 |
106 | #ifdef TLFormViewLayoutDebug
107 | self.backgroundColor = [UIColor colorWithRed:(rand() % 255)/255.0 green:(rand() % 255)/255.0 blue:(rand() % 255)/255.0 alpha:1.0];
108 | #endif
109 | }
110 |
111 | return self;
112 | }
113 |
114 | - (void)setupField:(BOOL)editing {
115 |
116 | }
117 |
118 | - (void)layoutSubviews {
119 | [super layoutSubviews];
120 |
121 | if (self.borderStyle != TLFormFieldBorderNone) {
122 |
123 | UIBezierPath *path = [UIBezierPath bezierPath];
124 | CGSize size = CGRectIntegral(self.bounds).size;
125 |
126 | if (self.borderStyle & TLFormFieldBorderTop) {
127 | [path moveToPoint:CGPointMake(0, 0.5)];
128 | [path addLineToPoint:CGPointMake(size.width, 0.5)];
129 | }
130 |
131 | if (self.borderStyle & TLFormFieldBorderRight) {
132 | [path moveToPoint:CGPointMake(size.width + 0.5, 0.5)];
133 | [path addLineToPoint:CGPointMake(size.width + 0.5, size.height + 0.5)];
134 | }
135 |
136 | if (self.borderStyle & TLFormFieldBorderBottom) {
137 | [path moveToPoint:CGPointMake(size.width + 0.5, size.height + 0.5)];
138 | [path addLineToPoint:CGPointMake(0.5, size.height + 0.5)];
139 | }
140 |
141 | if (self.borderStyle & TLFormFieldBorderLeft) {
142 | [path moveToPoint:CGPointMake(0.5, size.height)];
143 | [path addLineToPoint:CGPointMake(0.5, 0.5)];
144 | }
145 |
146 | CAShapeLayer *border = [CAShapeLayer layer];
147 | border.name = @"TLFomFieldBorderLayer";
148 | border.path = path.CGPath;
149 | border.strokeColor = [[UIColor colorWithRed:203/255.0 green:203/255.0 blue:207/255.0 alpha:1.0] CGColor];
150 |
151 | for (CALayer *layer in self.layer.sublayers) {
152 | if ([layer.name isEqualToString:@"TLFomFieldBorderLayer"]) {
153 | [layer removeFromSuperlayer];
154 | break;
155 | }
156 | }
157 |
158 | [self.layer addSublayer:border];
159 | }
160 | }
161 |
162 | - (NSDictionary *)defaultMetrics {
163 | return @{@"sp": @2.0, //small padding
164 | @"np": @8.0, //normal padding
165 | @"bp": @12.0}; //big padding
166 | }
167 |
168 | #pragma mark - Hidden
169 |
170 | - (void)setHidden:(BOOL)hidden {
171 | [super setHidden:hidden];
172 |
173 | if (hidden) {
174 | hiddenConstraint = [NSLayoutConstraint constraintWithItem:self
175 | attribute:NSLayoutAttributeHeight
176 | relatedBy:0
177 | toItem:nil
178 | attribute:NSLayoutAttributeNotAnAttribute
179 | multiplier:1.0
180 | constant:0.0];
181 | [self.superview addConstraint:hiddenConstraint];
182 | } else {
183 | [self.superview removeConstraint:hiddenConstraint];
184 | hiddenConstraint = nil;
185 | }
186 | }
187 |
188 | #pragma mark - Value
189 |
190 | - (void)setValue:(id)fieldValue {
191 |
192 | }
193 |
194 | - (id)getValue {
195 | return self.defautValue;
196 | }
197 |
198 | #pragma mark - Title View
199 |
200 | - (UIView *)titleView {
201 |
202 | if (_titleView)
203 | return _titleView;
204 |
205 | UILabel *title = [[UILabel alloc] init];
206 | title.numberOfLines = 2;
207 | title.lineBreakMode = NSLineBreakByWordWrapping;
208 | title.translatesAutoresizingMaskIntoConstraints = NO;
209 | title.text = self.title;
210 | title.tag = TLFormFieldTitleLabelTag;
211 | //Set the huggin priprity to make room for the field.
212 | [title setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
213 |
214 | if (self.helpText) {
215 | UIButton *showHelpButton = [[UIButton alloc] init];
216 | showHelpButton.translatesAutoresizingMaskIntoConstraints = NO;
217 | showHelpButton.titleLabel.font = [UIFont systemFontOfSize:25];
218 | [showHelpButton setTitle:@"?" forState:UIControlStateNormal];
219 | [showHelpButton setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
220 | [showHelpButton addTarget:self action:@selector(showHelpAction:) forControlEvents:UIControlEventTouchUpInside];
221 | showHelpButton.contentEdgeInsets = UIEdgeInsetsMake(1, 1, 1, 1);
222 |
223 | UIView *titleContainer = [[UIView alloc] init];
224 | titleContainer.translatesAutoresizingMaskIntoConstraints = NO;
225 | [titleContainer addSubview:title];
226 | [titleContainer addSubview:showHelpButton];
227 |
228 | NSDictionary *views = NSDictionaryOfVariableBindings(title, showHelpButton);
229 | [titleContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[title]-[showHelpButton]"
230 | options:NSLayoutFormatAlignAllCenterY
231 | metrics:nil
232 | views:views]];
233 | [titleContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[showHelpButton]|"
234 | options:NSLayoutFormatAlignAllCenterX
235 | metrics:nil
236 | views:views]];
237 | _titleView = titleContainer;
238 | } else
239 | _titleView = title;
240 |
241 | return _titleView;
242 | }
243 |
244 | - (void)showHelpAction:(UIButton *)sender {
245 | popover = [[UIPopoverController alloc] initWithContentViewController:[HelpTooltipPopoverControler helpTooltipControllerWithText:self.helpText]];
246 | CGRect finalFrame = [self convertRect:sender.frame fromView:sender.superview];
247 | [popover presentPopoverFromRect:finalFrame inView:self permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
248 | }
249 |
250 | @end
251 |
--------------------------------------------------------------------------------
/Example/TLFormView/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/23/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #import "TLFormView.h"
11 | #import "TLFormModel.h"
12 | #import "TLFormFieldDateTime.h"
13 | #import
14 |
15 |
16 | @interface UserModel : TLFormModel
17 |
18 | @property (nonatomic, strong) TLFormTitle *user_info;
19 | @property (nonatomic, strong) TLFormImage *avatar;
20 | @property (nonatomic, strong) TLFormText *name;
21 | @property (nonatomic, strong) TLFormNumber *age;
22 | @property (nonatomic, strong) TLFormBoolean *is_active;
23 | @property (nonatomic, strong) TLFormEnumerated *hobbies;
24 | @property (nonatomic, strong) TLFormDateTime *birthday;
25 | @property (nonatomic, strong) TLFormSeparator *separator;
26 | @property (nonatomic, strong) TLFormLongText *_description;
27 | @property (nonatomic, strong) TLFormList *friends;
28 |
29 | @end
30 |
31 | @implementation UserModel
32 |
33 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
34 |
35 | TLFormField *field = [super formView:form fieldForName:fieldName];
36 |
37 | //Add an explanation of the field. If this property has a value a quetion mark icon is display next to the field title.
38 | if ([fieldName isEqualToString:@"is_active"])
39 | field.helpText = @"A user is active when this value is true. Otherwise the user is not active (inactive) and this value shall be false. Only active users can have hobbies.";
40 |
41 | //The "hobbies" field will be visible only when the user "is active"
42 | else if ([fieldName isEqualToString:@"hobbies"])
43 | field.visibilityPredicate = [NSPredicate predicateWithFormat:@"$is_active.value == YES"];
44 |
45 | //Set the date field format
46 | else if ([fieldName isEqualToString:@"birthday"]) {
47 | TLFormFieldDateTime *birthdayField = (TLFormFieldDateTime *) field;
48 | birthdayField.dateFormat = @"MMMM dd, yyyy";
49 | birthdayField.pickerMode = UIDatePickerModeDate;
50 | }
51 |
52 | //Set all the borders when we are running on iPad
53 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
54 | field.borderStyle = TLFormFieldBorderAll;
55 |
56 | return field;
57 | }
58 |
59 | - (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form {
60 |
61 | //For iPhone use the default implementation on TLFormModel (UITableView-like layout)
62 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
63 | return [super constraintsFormatForFieldsInForm:form];
64 |
65 | //For iPad we are going to place the personal info next to the avatar image and the description and list of friends below
66 | else {
67 |
68 | return @[
69 | //Put the title at the top
70 | @"V:|-[user_info]",
71 | @"H:|-[user_info]-|",
72 |
73 | //Place the avatar on the top left
74 | @"V:[user_info]-[avatar(==230)]",
75 | @"H:|-[avatar]",
76 |
77 | //Now place all the first section fields to the right
78 | @"V:[user_info]-[name(>=44)]",
79 | @"H:|-[avatar]-[name]-|",
80 |
81 | @"V:[name]-[age(==name)]",
82 | @"H:|-[avatar(==420)]-[age]-|",
83 |
84 | @"V:[age]-[is_active(==name)]",
85 | @"H:|-[avatar]-[is_active]-|",
86 |
87 | @"V:[is_active]-[hobbies(==name@50)]", //Set a low priority to the height constraint so this is the one that the system will break when the field is hidden
88 | @"H:|-[avatar]-[hobbies]-|",
89 |
90 | //Add the separator
91 | @"V:[avatar]-[separator]",
92 | @"V:[hobbies]-[separator]",
93 | @"H:|-[separator]-|",
94 |
95 | //And the "description" and "firends" below
96 | @"V:[separator]-[_description]",
97 | @"H:|-[_description]-|",
98 |
99 | @"V:[_description]-[friends]-|",
100 | @"H:|-[friends]",
101 |
102 | @"V:[_description]-[birthday(>=44)]",
103 | @"H:|-[friends(==birthday)]-[birthday(==friends)]-|"
104 | ];
105 | }
106 | }
107 |
108 | @end
109 |
110 |
111 |
112 |
113 |
114 |
115 | @interface ViewController ()
116 | @property (weak, nonatomic) IBOutlet TLFormView *form;
117 | @end
118 |
119 | @implementation ViewController {
120 | UserModel *user;
121 | }
122 |
123 | - (void)viewDidLoad {
124 | [super viewDidLoad];
125 |
126 | //Create and setup the object that describe the form.
127 | user = [[UserModel alloc] init];
128 | [self setupFormValues];
129 |
130 | //Set the model
131 | [self.form setFormModel:user];
132 |
133 | //Make some visual tweaks
134 | self.form.margin = 0.0;
135 | self.form.backgroundColor = [UIColor colorWithRed:239/255.0 green:239/255.0 blue:244/255.0 alpha:1.0];
136 |
137 | //Show the edit button
138 | self.navigationItem.rightBarButtonItem = self.editButtonItem;
139 | }
140 |
141 | - (void)setupFormValues {
142 | user.name = TLFormTextValue(@"John Doe");
143 | user.age = TLFormNumberValue(@42);
144 | user.is_active = TLFormBooleanValue(YES);
145 | user.friends = TLFormListValue(@[@"friend 0", @"friend 1", @"friend 2", @"friend 3", @"friend 4"]);
146 | user.hobbies = TLFormEnumeratedValue(@"3D printing", @[@"3D printing", @"Amateur radio", @"Acting"]);
147 |
148 | user._description = TLFormLongTextValue(@"Michael O. Church about OOP: \"OOP tries to make software look like \"the real world\" as can be understood by an average person. (CheckingAccount extends Account extends HasBalance extends Object). The problem is that it encourages people to program before they think, and it allows software to be created that mostly works but no one knows why it does.\"");
149 | //Check about this passage here: http://www.quora.com/Was-object-oriented-programming-a-failure
150 |
151 | NSURL *url = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/custom_covers/216x146/413557246971119139_1385652535.jpg"];
152 | user.avatar = TLFormImageValue(url);
153 | user.birthday = TLFormDateTimeValue([NSDate date]);
154 | }
155 |
156 | #pragma mark - Bar Buttons Actions
157 |
158 | - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
159 | [super setEditing:editing animated:animated];
160 |
161 | if (editing) {
162 | UIBarButtonItem *undo = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoAction:)];
163 | self.navigationItem.leftBarButtonItem = undo;
164 | } else
165 | self.navigationItem.leftBarButtonItem = nil;
166 |
167 | self.form.editing = editing;
168 | [self.form setupFields];
169 | }
170 |
171 | - (void)undoAction:(id)sender {
172 | [self setupFormValues];
173 | [self setEditing:NO animated:YES];
174 | }
175 |
176 | #pragma mark - TLFormViewDelegate
177 |
178 | - (void)formView:(TLFormView *)form didSelectField:(TLFormField *)field {
179 | //The field name is the same that the property name set in the UserModel class
180 | if (self.editing && [field.fieldName isEqualToString:@"avatar"]) {
181 |
182 | [self.view endEditing:YES];
183 |
184 | UIActionSheet *popup = [[UIActionSheet alloc] initWithTitle:nil
185 | delegate:self
186 | cancelButtonTitle:@"cancel"
187 | destructiveButtonTitle:nil
188 | otherButtonTitles:@"camera", @"photo library", nil];
189 | [popup showInView:self.view];
190 | }
191 | }
192 |
193 | #pragma mark - UIActionSheetDelegate
194 |
195 | - (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex {
196 |
197 | switch (buttonIndex) {
198 | case 0:
199 | [self performSelector:@selector(useCamera) withObject:nil afterDelay:0.1];
200 | break;
201 | case 1:
202 | [self performSelector:@selector(useCameraRoll) withObject:nil afterDelay:0.1];
203 | break;
204 | default:
205 | break;
206 | }
207 | }
208 |
209 | - (void)useCamera {
210 | if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
211 |
212 | UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
213 | imagePicker.delegate = self;
214 | imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
215 | imagePicker.mediaTypes = @[(NSString *) kUTTypeImage];
216 | imagePicker.allowsEditing = NO;
217 | [self presentViewController:imagePicker animated:YES completion:nil];
218 | }
219 | }
220 |
221 |
222 | - (void)useCameraRoll {
223 | if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
224 |
225 | UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
226 | imagePicker.delegate = self;
227 | imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
228 | imagePicker.mediaTypes = @[(NSString *) kUTTypeImage];
229 | imagePicker.allowsEditing = NO;
230 | [self presentViewController:imagePicker animated:YES completion:nil];
231 | }
232 | }
233 |
234 |
235 | #pragma mark - UIImagePickerControllerDelegate
236 |
237 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
238 | [self dismissViewControllerAnimated:YES completion:nil];
239 | }
240 |
241 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
242 | [self dismissViewControllerAnimated:YES completion:nil];
243 |
244 | NSString *mediaType = info[UIImagePickerControllerMediaType];
245 | if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
246 |
247 | UIImage *newImage = info[UIImagePickerControllerOriginalImage];
248 |
249 | //If there is no ref url the image need to be saved
250 | NSURL *refUrl = info[UIImagePickerControllerReferenceURL];
251 | if (!refUrl)
252 | UIImageWriteToSavedPhotosAlbum(newImage, nil, nil, nil);
253 |
254 | user.avatar = TLFormImageValue(newImage);
255 | [self.form reloadValues];
256 | }
257 | }
258 |
259 | @end
260 |
--------------------------------------------------------------------------------
/Pod/Classes/Fields/TLFormFieldImage.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormFieldImage.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormFieldImage.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 |
14 | NSString * const TLFormFieldNoImageName = @"tlformfieldnoimage.png";
15 |
16 |
17 |
18 | @implementation TLFormFieldImage {
19 | UIImageView *imageView;
20 | NSLayoutConstraint *imageViewHeight;
21 | //The reference used to store the image (get/set)value
22 | id imageRefValue;
23 | NSURLSessionDownloadTask *imageDownloadTask;
24 |
25 | }
26 |
27 | - (void)setupField:(BOOL)editing {
28 | [super setupField:editing];
29 |
30 | imageView = [[UIImageView alloc] init];
31 | imageView.contentMode = UIViewContentModeScaleAspectFit;
32 | imageView.translatesAutoresizingMaskIntoConstraints = NO;
33 | [self addSubview:imageView];
34 |
35 | imageViewHeight = [NSLayoutConstraint constraintWithItem:imageView
36 | attribute:NSLayoutAttributeHeight
37 | relatedBy:0
38 | toItem:nil
39 | attribute:NSLayoutAttributeNotAnAttribute
40 | multiplier:1.0 constant:0];
41 |
42 | UIButton *tapRecognizer = [[UIButton alloc] init];
43 | tapRecognizer.translatesAutoresizingMaskIntoConstraints = NO;
44 | [tapRecognizer addTarget:self action:@selector(imageSelectedAction) forControlEvents:UIControlEventTouchUpInside];
45 | [self addSubview:tapRecognizer];
46 |
47 | NSDictionary *views;
48 |
49 | if (editing) {
50 | UIView *title = [self titleView];
51 | [self addSubview:title];
52 |
53 | UILabel *titleLabel = (UILabel *) [title viewWithTag:TLFormFieldTitleLabelTag];
54 | titleLabel.textColor = [UIColor grayColor];
55 |
56 | //Set the vertical hugging priority to "requiered" so the title label allways take the minimum space requiered
57 | [title setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
58 |
59 | views = NSDictionaryOfVariableBindings(imageView, tapRecognizer, title);
60 |
61 | //Size the title to the top of the field taking all the widht
62 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-np-[title]-np-|"
63 | options:0
64 | metrics:self.defaultMetrics
65 | views:views]];
66 | //Constraint the imageView to the size of the super view
67 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[title][imageView]|"
68 | options:0
69 | metrics:self.defaultMetrics
70 | views:views]];
71 |
72 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-np-[imageView]-np-|"
73 | options:0
74 | metrics:self.defaultMetrics
75 | views:views]];
76 | //Constraint the tapRecognizer to the size of the super view
77 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[title][tapRecognizer]|"
78 | options:0
79 | metrics:self.defaultMetrics
80 | views:views]];
81 |
82 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tapRecognizer]|"
83 | options:0
84 | metrics:self.defaultMetrics
85 | views:views]];
86 | } else {
87 | views = NSDictionaryOfVariableBindings(imageView, tapRecognizer);
88 |
89 | //Constraint the imageView to the size of the super view
90 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView]|"
91 | options:0
92 | metrics:self.defaultMetrics
93 | views:views]];
94 |
95 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]|"
96 | options:0
97 | metrics:self.defaultMetrics
98 | views:views]];
99 |
100 | //Constraint the tapRecognizer to the size of the super view
101 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[tapRecognizer]|"
102 | options:0
103 | metrics:self.defaultMetrics
104 | views:views]];
105 |
106 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tapRecognizer]|"
107 | options:0
108 | metrics:self.defaultMetrics
109 | views:views]];
110 | }
111 |
112 | [self setValue:self.defautValue];
113 | }
114 |
115 | - (void)layoutSubviews {
116 | [super layoutSubviews];
117 |
118 | //If the image is too big the "scale to aspect fit" scale the image in one dimention but not the other. This fix the case where the image fit the width but not the height.
119 | //TODO: Add the same logic for the width is trivial, the thing is how this affect all the possible layouts. This may need other approach
120 | if (imageView.image) {
121 | UIImage *image = imageView.image;
122 | CGFloat multiplier = (CGRectGetWidth(imageView.bounds) / image.size.width);
123 | CGFloat scaledHeight = multiplier * image.size.height;
124 |
125 | [self removeConstraint:imageViewHeight];
126 |
127 | if (scaledHeight < imageView.bounds.size.height) {
128 | [imageViewHeight setConstant:scaledHeight];
129 | [self addConstraint:imageViewHeight];
130 | }
131 | }
132 | }
133 |
134 | - (void)setValue:(id)fieldValue {
135 |
136 | imageRefValue = fieldValue;
137 |
138 | //Update the image and invalidate the layout
139 | void (^updateImage)(UIImage *) = ^(UIImage *newImage) {
140 | imageView.image = newImage;
141 | [imageView setNeedsLayout];
142 | };
143 |
144 | if (!fieldValue)
145 | updateImage([UIImage imageNamed:TLFormFieldNoImageName]);
146 |
147 | else if ([fieldValue isKindOfClass:[UIImage class]])
148 | updateImage(fieldValue);
149 |
150 | //If the value is an URL
151 | else if ([fieldValue isKindOfClass:[NSURL class]]) {
152 |
153 | //Show a spinner on the image view while download the image
154 | UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
155 | [spinner startAnimating];
156 | spinner.translatesAutoresizingMaskIntoConstraints = NO;
157 | [imageView addSubview:spinner];
158 | [imageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[spinner]-|"
159 | options:0
160 | metrics:nil
161 | views:NSDictionaryOfVariableBindings(spinner)]];
162 | [imageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[spinner]-|"
163 | options:0
164 | metrics:nil
165 | views:NSDictionaryOfVariableBindings(spinner)]];
166 |
167 | void (^downloadCompleteHandler)(NSURL *, NSURLResponse *, NSError *) = ^(NSURL *location, NSURLResponse *response, NSError *error){
168 |
169 | UIImage *image;
170 |
171 | if (!error) {
172 | //We need to add the extension to the file name so get it from the 'fieldValue' and append it to the tmp file
173 | NSString *finalPath = [[location path] stringByAppendingPathExtension:[fieldValue pathExtension]];
174 |
175 | //Try to rename the file
176 | NSError *error;
177 | [[NSFileManager defaultManager] moveItemAtPath:[location path] toPath:finalPath error:&error];
178 |
179 | if (!error)
180 | image = [UIImage imageWithContentsOfFile:finalPath];
181 | else {
182 | NSLog(@"TLFormView: Error moving tmp file at url: %@ - Error: %@", location, error);
183 | image = [UIImage imageNamed:TLFormFieldNoImageName];
184 | }
185 |
186 | } else {
187 | NSLog(@"TLFormView: Error getting image with url: %@ - Error: %@", fieldValue, error);
188 | image = [UIImage imageNamed:TLFormFieldNoImageName];
189 | }
190 |
191 | dispatch_async(dispatch_get_main_queue(), ^{
192 | [spinner removeFromSuperview];
193 | updateImage(image);
194 | });
195 |
196 | };
197 |
198 | //Cancel any previous request before start a new one
199 | [imageDownloadTask cancel];
200 | imageDownloadTask = [[NSURLSession sharedSession] downloadTaskWithRequest:[NSURLRequest requestWithURL:fieldValue] completionHandler:downloadCompleteHandler];
201 | [imageDownloadTask resume];
202 | }
203 | }
204 |
205 | - (id)getValue {
206 | return imageRefValue;
207 | }
208 |
209 | - (void)imageSelectedAction {
210 | [self.formDelegate didSelectField:self];
211 | }
212 |
213 | @end
214 |
--------------------------------------------------------------------------------
/Pod/Classes/TLFormModel.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormModel.m
3 | // TLFormView
4 | //
5 | // Created by Bruno Berisso on 2/24/15.
6 | // Copyright (c) 2015 Bruno Berisso. All rights reserved.
7 | //
8 |
9 | #import "TLFormModel.h"
10 | #import
11 | #import "TLFormAllFields.h"
12 |
13 |
14 | @implementation TLFormSeparator : NSObject @end
15 | TLFormSeparator * TLFormSeparatorValue () {
16 | return [TLFormSeparator new];
17 | }
18 |
19 | @implementation TLFormText : NSString @end
20 | TLFormText * TLFormTextValue(NSString *text) {
21 | return (TLFormText *) [text copy];
22 | }
23 |
24 | @implementation TLFormLongText : NSString @end
25 | TLFormLongText * TLFormLongTextValue(NSString *longText) {
26 | return (TLFormLongText *) [longText copy];
27 | }
28 |
29 | @implementation TLFormTitle : NSString @end
30 | TLFormTitle * TLFormTitleValue (NSString *title) {
31 | return (TLFormTitle *) [title copy];
32 | }
33 |
34 | @implementation TLFormNumber : NSNumber @end
35 | TLFormNumber * TLFormNumberValue (NSNumber *number) {
36 | return (TLFormNumber *) [number copy];
37 | }
38 |
39 | @implementation TLFormBoolean : NSNumber @end
40 | TLFormBoolean * TLFormBooleanValue (BOOL boolean) {
41 | return (TLFormBoolean *) [TLFormBoolean numberWithBool:boolean];
42 | }
43 |
44 | @implementation TLFormEnumerated : NSDictionary @end
45 | NSString * const TLFormEnumeratedSelectedValue = @"TLFormEnumeratedSelectedValue";
46 | NSString * const TLFormEnumeratedAllValues = @"TLFormEnumeratedAllValues";
47 |
48 | TLFormEnumerated * TLFormEnumeratedValue (id current, NSArray *all) {
49 | return (TLFormEnumerated *) @{TLFormEnumeratedSelectedValue: current,
50 | TLFormEnumeratedAllValues: all};
51 | }
52 |
53 | @implementation TLFormList : NSArray @end
54 | TLFormList * TLFormListValue(NSArray *array) {
55 | return (TLFormList *) [array copy];
56 | }
57 |
58 | @implementation TLFormImage : NSObject @end
59 | TLFormImage * TLFormImageValue (NSObject *urlOrImage) {
60 | if ([urlOrImage isKindOfClass:[UIImage class]] || [urlOrImage isKindOfClass:[NSURL class]]) {
61 | return (TLFormImage *) [urlOrImage copy];
62 | } else {
63 | [NSException raise:@"Invalid image type" format:@"Should be UIImage or NSURL but it is: %@", NSStringFromClass([urlOrImage class])];
64 | return nil;
65 | }
66 | }
67 |
68 | @implementation TLFormDateTime : NSDate @end
69 | TLFormDateTime * TLFormDateTimeValue (NSDate *date) {
70 | return (TLFormDateTime *) [date copy];
71 | }
72 |
73 |
74 | typedef enum {
75 | TLFormValueTypeUnknow = 0,
76 | TLFormValueTypeSeparator,
77 | TLFormValueTypeText,
78 | TLFormValueTypeLongText,
79 | TLFormValueTypeTitle,
80 | TLFormValueTypeNumber,
81 | TLFormValueTypeBoolean,
82 | TLFormValueTypeEnumerated,
83 | TLFormValueTypeList,
84 | TLFormValueTypeImage,
85 | TLFormValueTypeDateTime
86 | } TLFormValueType;
87 |
88 |
89 |
90 | @interface TLPropertyInfo : NSObject
91 |
92 | @property (nonatomic, strong) NSString *name;
93 | @property (nonatomic, readonly) Class fieldClass;
94 | @property (nonatomic, readonly) NSString *title;
95 | @property (nonatomic, readonly) TLFormValueType valueType;
96 |
97 | + (instancetype)withObjcProperty:(objc_property_t)property;
98 |
99 | @end
100 |
101 | @implementation TLPropertyInfo
102 |
103 | + (instancetype)withObjcProperty:(objc_property_t)property {
104 |
105 | TLPropertyInfo *pi = nil;
106 | //Example value: T@"PROPERTY_TYPE",&,N,V_avatar
107 | NSString *propertyType = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
108 |
109 | //Parse the property type string to get the class
110 | if (propertyType.length > 4) {
111 |
112 | propertyType = [propertyType substringFromIndex:3];
113 | NSUInteger rightQuoteIdx = [propertyType rangeOfString:@"\""].location;
114 |
115 | if (rightQuoteIdx != NSNotFound) {
116 | propertyType = [propertyType substringToIndex:rightQuoteIdx];
117 |
118 | TLFormValueType valueType = [TLPropertyInfo valueTypeFromString:propertyType];
119 | if (valueType != TLFormValueTypeUnknow) {
120 |
121 | pi = [[TLPropertyInfo alloc] init];
122 | pi.name = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
123 | pi->_valueType = valueType;
124 | [pi setupFieldInfo];
125 | }
126 | }
127 | }
128 |
129 | return pi;
130 | }
131 |
132 | + (TLFormValueType)valueTypeFromString:(NSString *)stringType {
133 | //A quick way to turn a class name to an enumerated type.
134 |
135 | NSUInteger idx = [@[@"TLFormSeparator",
136 | @"TLFormText",
137 | @"TLFormLongText",
138 | @"TLFormTitle",
139 | @"TLFormNumber",
140 | @"TLFormBoolean",
141 | @"TLFormEnumerated",
142 | @"TLFormList",
143 | @"TLFormImage",
144 | @"TLFormDateTime"] indexOfObject:stringType];
145 |
146 | if (idx != NSNotFound)
147 | return (TLFormValueType) idx + 1;
148 | else
149 | return TLFormValueTypeUnknow;
150 | }
151 |
152 | - (void)setupFieldInfo {
153 |
154 | //Map the value types to field and input types to define de behaviour of each type of value
155 |
156 | switch (self.valueType) {
157 |
158 | case TLFormValueTypeSeparator:
159 | _fieldClass = [TLFormFieldTitle class];
160 | break;
161 |
162 | case TLFormValueTypeLongText:
163 | _fieldClass = [TLFormFieldMultiLine class];
164 | break;
165 |
166 | case TLFormValueTypeTitle:
167 | _fieldClass = [TLFormFieldTitle class];
168 | break;
169 |
170 | case TLFormValueTypeText:
171 | _fieldClass = [TLFormFieldSingleLine class];
172 | break;
173 |
174 | case TLFormValueTypeNumber:
175 | _fieldClass = [TLFormFieldNumeric class];
176 | break;
177 |
178 | case TLFormValueTypeBoolean:
179 | _fieldClass = [TLFormFieldYesNo class];
180 | break;
181 |
182 | case TLFormValueTypeEnumerated:
183 | _fieldClass = [TLFormFieldSelect class];
184 | break;
185 |
186 | case TLFormValueTypeList:
187 | _fieldClass = [TLFormFieldList class];
188 | break;
189 |
190 | case TLFormValueTypeImage:
191 | _fieldClass = [TLFormFieldImage class];
192 | break;
193 |
194 | case TLFormValueTypeDateTime:
195 | _fieldClass = [TLFormFieldDateTime class];
196 | break;
197 |
198 | default:
199 | [NSException raise:@"Invalid form value type" format:@"Raw value: %d", self.valueType];
200 | break;
201 | }
202 |
203 |
204 | //The separator are a TLFormTitle with an empty title
205 | if (self.valueType == TLFormValueTypeSeparator)
206 | _title = @" ";
207 | else {
208 | //For the rest of te fields the title is calculated from the property name converting the "_" in spaces and
209 | //capitalising all the words (ex: "some_property_name" -> "Some Property Name")
210 | for (NSString *titleComp in [self.name componentsSeparatedByString:@"_"]) {
211 | if (_title.length > 0)
212 | _title = [_title stringByAppendingFormat:@" %@", [titleComp capitalizedString]];
213 | else
214 | _title = [titleComp capitalizedString];
215 | }
216 | }
217 | }
218 |
219 | @end
220 |
221 |
222 |
223 | @interface TLFormModel ()
224 |
225 | @end
226 |
227 |
228 | @implementation TLFormModel {
229 | NSMutableArray *propertiesInfo;
230 | NSArray *propertiesIndex;
231 | }
232 |
233 | - (TLPropertyInfo *)infoFormFieldWithName:(NSString *)fieldName {
234 | return propertiesInfo[[propertiesIndex indexOfObject:fieldName]];
235 | }
236 |
237 | #pragma mark - TLFormViewDataSource
238 |
239 | - (NSArray *)fieldNamesToShowInFormView:(TLFormView *)form {
240 |
241 | //This is the first method called to setup the form so we parse our properties and fill the 'propertiesInfo' list with objects PropertyInfo
242 |
243 | unsigned int count = 0;
244 | objc_property_t *properties = class_copyPropertyList([self class], &count);
245 | propertiesInfo = [NSMutableArray arrayWithCapacity:count];
246 |
247 | for (int i = 0; i < count; i++ ) {
248 |
249 | objc_property_t property = properties[i];
250 | TLPropertyInfo *propertyInfo = [TLPropertyInfo withObjcProperty:property];
251 |
252 | if (propertyInfo)
253 | [propertiesInfo addObject:propertyInfo];
254 | }
255 |
256 | free(properties);
257 |
258 | //The 'name' in PropertyInfo is the 'fieldName' in the TLFormField
259 | propertiesIndex = [propertiesInfo valueForKey:@"name"];
260 | return propertiesIndex;
261 | }
262 |
263 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
264 |
265 | TLPropertyInfo *fieldInfo = [self infoFormFieldWithName:fieldName];
266 | id value = nil;
267 | NSArray *choices;
268 |
269 | if (fieldInfo.valueType == TLFormValueTypeTitle)
270 | value = fieldInfo.title;
271 | else {
272 | value = [self valueForKey:fieldName];
273 |
274 | //Get the choices for the enumerated type
275 | if (fieldInfo.valueType == TLFormValueTypeEnumerated) {
276 | choices = value[TLFormEnumeratedAllValues];
277 | value = value[TLFormEnumeratedSelectedValue];
278 | }
279 | }
280 |
281 | TLFormField *field = [fieldInfo.fieldClass formFieldWithName:fieldName title:fieldInfo.title andDefaultValue:value];
282 |
283 | //Set the properties specific for the single line field class
284 | if ([fieldInfo.fieldClass isSubclassOfClass:[TLFormFieldSelect class]]) {
285 |
286 | TLFormFieldSelect *singleLineField = (TLFormFieldSelect *) field;
287 | singleLineField.choicesValues = choices;
288 |
289 | } else if ([fieldInfo.fieldClass isSubclassOfClass:[TLFormFieldList class]]) {
290 |
291 | TLFormFieldList *listField = (TLFormFieldList *) field;
292 | listField.delegate = self;
293 | }
294 |
295 | //Set the top and bottom borders
296 | field.borderStyle = TLFormFieldBorderTop | TLFormFieldBorderBottom;
297 |
298 | return field;
299 | }
300 |
301 | - (id)formView:(TLFormView *)form valueForFieldWithName:(NSString *)fieldName {
302 | TLPropertyInfo *fieldInfo = [self infoFormFieldWithName:fieldName];
303 | id value = [self valueForKey:fieldName];
304 |
305 | switch (fieldInfo.valueType) {
306 | case TLFormValueTypeEnumerated:
307 | return value[TLFormEnumeratedSelectedValue];
308 |
309 | case TLFormValueTypeTitle:
310 | return fieldInfo.title;
311 |
312 | default:
313 | return value;
314 | }
315 | }
316 |
317 | - (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form {
318 |
319 | //The default layout is present the fields in a column in the same order that the property declaration has.
320 |
321 | NSMutableArray *constraints = [NSMutableArray array];
322 |
323 | for (NSUInteger i = 0; i < propertiesIndex.count; i++) {
324 |
325 | NSString *propertyName = propertiesIndex[i];
326 | NSString *verticalConsFormat;
327 |
328 | if ([propertiesIndex firstObject] == propertyName)
329 | verticalConsFormat = @"V:|-margin-[%@(>=44)]";
330 |
331 | else {
332 |
333 | NSString *previousProperty = propertiesIndex[i - 1];
334 | verticalConsFormat = [NSString stringWithFormat:@"V:[%@(>=44)]-(==0)-[%%@(>=44)]", previousProperty];
335 |
336 | if ([propertiesIndex lastObject] == propertyName)
337 | verticalConsFormat = [verticalConsFormat stringByAppendingString:@"-margin-|"];
338 | }
339 |
340 | [constraints addObject:[NSString stringWithFormat:verticalConsFormat, propertyName]];
341 | [constraints addObject:[NSString stringWithFormat:@"|-margin-[%@]-margin-|", propertyName]];
342 | }
343 |
344 | return constraints;
345 | }
346 |
347 | #pragma mark - TLFormViewDelegate
348 |
349 | - (void)formView:(TLFormView *)form didChangeValueForField:(TLFormField *)field newValue:(id)value {
350 | NSString *fieldName = field.fieldName;
351 | TLPropertyInfo *fieldInfo = [self infoFormFieldWithName:fieldName];
352 |
353 | if (fieldInfo.valueType == TLFormValueTypeEnumerated) {
354 | NSMutableDictionary *enumValue = [[self valueForKey:fieldName] mutableCopy];
355 | [enumValue setObject:value forKey:TLFormEnumeratedSelectedValue];
356 | [self setValue:enumValue forKey:fieldName];
357 | } else
358 | [self setValue:value forKey:fieldName];
359 | }
360 |
361 | #pragma mark - TLFormFieldListDelegate
362 |
363 | - (void)listFormField:(TLFormField *)field didDeleteRowAtIndexPath:(NSIndexPath *)indexPath {
364 | NSMutableArray *listValue = [[self valueForKey:field.fieldName] mutableCopy];
365 | [listValue removeObjectAtIndex:indexPath.row];
366 | [self setValue:listValue forKey:field.fieldName];
367 | }
368 |
369 | - (BOOL)listFormField:(TLFormField *)field canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
370 | return YES;
371 | }
372 |
373 | - (void)listFormField:(TLFormField *)field moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
374 | NSMutableArray *listValue = [[self valueForKey:field.fieldName] mutableCopy];
375 |
376 | id sourceObj = listValue[sourceIndexPath.row];
377 | id destObj = listValue[destinationIndexPath.row];
378 |
379 | [listValue replaceObjectAtIndex:sourceIndexPath.row withObject:destObj];
380 | [listValue replaceObjectAtIndex:destinationIndexPath.row withObject:sourceObj];
381 |
382 | [self setValue:listValue forKey:field.fieldName];
383 | }
384 |
385 | @end
386 |
387 |
--------------------------------------------------------------------------------
/Pod/Classes/TLFormView.m:
--------------------------------------------------------------------------------
1 | //
2 | // TLFormView.m
3 | // GT
4 | //
5 | // Created by Bruno Berisso on 3/19/14.
6 | // Copyright (c) 2014 Gathered Table LLC. All rights reserved.
7 | //
8 |
9 | #import "TLFormView.h"
10 | #import "TLFormField+Protected.h"
11 |
12 |
13 | @interface TLFormView ()
14 | @end
15 |
16 |
17 | @implementation TLFormView {
18 | NSMutableDictionary *viewFieldMap;
19 | UIView *containerView;
20 | NSMutableArray *fieldsWithVisibilityPredicate;
21 | BOOL needsReload;
22 | TLFormField *selectedField;
23 | UIEdgeInsets defaultInsets;
24 | id formModel;
25 | }
26 |
27 | #pragma mark Initialization
28 |
29 | - (id)initWithFrame:(CGRect)frame {
30 | self = [super initWithFrame:frame];
31 |
32 | if (self)
33 | [self privateInit];
34 |
35 | return self;
36 | }
37 |
38 | - (void)awakeFromNib {
39 | [super awakeFromNib];
40 | [self privateInit];
41 | }
42 |
43 | - (void)privateInit {
44 | //Map the field names with the corresponding views. Used to resolv the autolayout format strings
45 | viewFieldMap = [NSMutableDictionary dictionary];
46 |
47 | //Fire an automaric reload on the view first display.
48 | needsReload = YES;
49 |
50 | //Handle the keyboard presentation
51 | NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
52 |
53 | [defaultCenter removeObserver:self];
54 | [defaultCenter addObserver:self selector:@selector(handleKeyboardShow:) name:UIKeyboardDidShowNotification object:nil];
55 | [defaultCenter addObserver:self selector:@selector(handleKeyboardHide:) name:UIKeyboardDidHideNotification object:nil];
56 | [defaultCenter addObserver:self selector:@selector(orientationChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
57 |
58 | //Dismiss the keyboard on scroll
59 | self.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
60 |
61 | //This view is used to control how the fields are layed out inside the form. We need to do it this way because of how the scroll view
62 | //manage their content and how the scroll is implemented. (See: http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/index.html
63 | //and http://www.g8production.com/post/57513133020/auto-layout-with-uiscrollview-how-to-use )
64 |
65 | containerView = [[UIView alloc] init];
66 | containerView.translatesAutoresizingMaskIntoConstraints = NO;
67 | [self addSubview:containerView];
68 |
69 | //Set the margin to a default value
70 | self.margin = 8.0;
71 |
72 | #ifdef TLFormViewLayoutDebug
73 | self.backgroundColor = [UIColor redColor];
74 | containerView.backgroundColor = [UIColor blueColor];
75 | #endif
76 | }
77 |
78 | - (void)dealloc {
79 | //Remove from notification center on dealloc
80 | [[NSNotificationCenter defaultCenter] removeObserver:self];
81 | }
82 |
83 | - (void)layoutSubviews {
84 | [super layoutSubviews];
85 |
86 | if (needsReload) {
87 | needsReload = NO;
88 | [self performSelectorOnMainThread:@selector(setupFields) withObject:nil waitUntilDone:NO];
89 | }
90 | }
91 |
92 | - (void)setFormDataSource:(id)formDataSource {
93 | if (formModel)
94 | [NSException raise:@"Inconsisten form setup" format:@"A previous data source was set with a form model (setFromModel:). Can't decide which one should prevail."];
95 | else
96 | _formDataSource = formDataSource;
97 | }
98 |
99 | #pragma mark - TLFormFieldDelegate
100 |
101 | - (void)setFormModel:(id)model {
102 | self.formDataSource = model;
103 | formModel = model;
104 | }
105 |
106 | - (void)didSelectField:(TLFormField *)field {
107 |
108 | //Remember the selected field for handling the keyboard
109 | selectedField = field;
110 |
111 | if ([self.formDelegate respondsToSelector:@selector(formView:didSelectField:)])
112 | [self.formDelegate formView:self didSelectField:field];
113 |
114 | if ([formModel respondsToSelector:@selector(formView:didSelectField:)])
115 | [formModel formView:self didSelectField:field];
116 | }
117 |
118 | - (void)didChangeValueForField:(TLFormField *)field newValue:(id)value {
119 | if ([self.formDelegate respondsToSelector:@selector(formView:didChangeValueForField:newValue:)])
120 | [self.formDelegate formView:self didChangeValueForField:field newValue:value];
121 |
122 | if ([formModel respondsToSelector:@selector(formView:didChangeValueForField:newValue:)])
123 | [formModel formView:self didChangeValueForField:field newValue:value];
124 |
125 | [self updateFieldsVisibility];
126 | }
127 |
128 | #pragma mark - Form fields setup
129 |
130 | - (void)setupFields {
131 |
132 | //Remove any previous layout definition
133 | [containerView removeConstraints:containerView.constraints];
134 | //and remove all subviews
135 | [[containerView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
136 |
137 | //Reset the list of fields with variable visibility
138 | fieldsWithVisibilityPredicate = [NSMutableArray array];
139 |
140 | //Ask the data source for the field names
141 | for (NSString *fieldName in [self.formDataSource fieldNamesToShowInFormView:self]) {
142 |
143 | //for every field name ask for the TLFormField
144 | TLFormField *field = [self.formDataSource formView:self fieldForName:fieldName];
145 |
146 | //Setup the field internal state
147 | [field setupField:self.editing];
148 |
149 | [self addField:field];
150 | }
151 |
152 | //Once all fields are added ask for the layout rules and apply it to the container view
153 | NSArray *layout = [self.formDataSource constraintsFormatForFieldsInForm:self];
154 | [self setupLayoutWithConstraints:layout];
155 |
156 | //Ask the datasource for the actual values to show
157 | [self reloadValues];
158 | }
159 |
160 | - (void)addField:(TLFormField *)field {
161 | //Set ourself as the field delegate
162 | field.formDelegate = self;
163 | //update the view-fieldName map
164 | [viewFieldMap setValue:field forKey:field.fieldName];
165 | //add the field to the view tree
166 | [containerView addSubview:field];
167 |
168 | //Register the field if has variable visibility
169 | if (field.visibilityPredicate)
170 | [fieldsWithVisibilityPredicate addObject:field];
171 | }
172 |
173 | #pragma mark - Layout Setup
174 |
175 | - (void)adjustAuxiliarViewInternalLayout:(UIView *)auxView {
176 |
177 | auxView.translatesAutoresizingMaskIntoConstraints = NO;
178 |
179 | NSMutableDictionary *views = [NSMutableDictionary dictionary];
180 | NSMutableString *vConstraints = [NSMutableString stringWithString:@"V:|-"];
181 | NSMutableArray *hConstraints = [NSMutableArray array];
182 |
183 | //Setup a set of default constraints, arrange the subviews centered in a column. This ensure that the height of the scroll can be calculated
184 |
185 | for (int i = 0; i < auxView.subviews.count; i++) {
186 | NSString *key = [NSString stringWithFormat:@"subView%d", i];
187 | UIView *subView = auxView.subviews[i];
188 |
189 | subView.translatesAutoresizingMaskIntoConstraints = NO;
190 | [views setObject:subView forKey:key];
191 |
192 | [vConstraints appendFormat:@"margin-[%@]-", key];
193 | [hConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subView(==auxView)]|"
194 | options:0
195 | metrics:nil
196 | views:NSDictionaryOfVariableBindings(subView, auxView)]];
197 | }
198 |
199 | [vConstraints appendString:@"margin-|"];
200 |
201 | //Add the vertical and horizontal constraints
202 | [auxView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vConstraints
203 | options:0
204 | metrics:@{@"margin": @(self.margin)}
205 | views:views]];
206 | [auxView addConstraints:hConstraints];
207 | }
208 |
209 | - (void)setupLayoutWithConstraints:(NSArray *)constraintFomats {
210 |
211 | //This make the margin available in the rules
212 | NSDictionary *defaultMetrics = @{@"margin": @(self.margin)};
213 |
214 | for (NSString *formatString in constraintFomats) {
215 |
216 | //Remove all the spaces
217 | NSString *constraintFormat = [formatString stringByReplacingOccurrencesOfString:@" " withString:@""];
218 | if (constraintFormat.length > 0) {
219 |
220 | NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:constraintFormat
221 | options:0
222 | metrics:defaultMetrics
223 | views:viewFieldMap];
224 | [containerView addConstraints:constraints];
225 | }
226 | }
227 |
228 | //Add constraints for the content view to adjust the size to the form
229 | NSMutableDictionary *views = [NSMutableDictionary dictionaryWithObjects:@[containerView, self] forKeys:@[@"containerView", @"self"]];
230 |
231 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[containerView(==self)]|"
232 | options:0
233 | metrics:0
234 | views:views]];
235 |
236 | //Add the header and footer
237 | if (self.header) {
238 | [self adjustAuxiliarViewInternalLayout:self.footer];
239 | [self addSubview:self.header];
240 |
241 | [views setObject:self.header forKey:@"header"];
242 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[header(==self)]|"
243 | options:0
244 | metrics:0
245 | views:views]];
246 | }
247 |
248 | if (self.footer) {
249 | [self adjustAuxiliarViewInternalLayout:self.footer];
250 | [self addSubview:self.footer];
251 |
252 | [views setObject:self.footer forKey:@"footer"];
253 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[footer(==self)]|"
254 | options:0
255 | metrics:0
256 | views:views]];
257 | }
258 |
259 | NSString *verticalConstraintFormat = [NSString stringWithFormat:@"V:|%@[containerView]%@|",
260 | self.header ? @"[header]" : @"",
261 | self.footer ? @"[footer]" : @""];
262 |
263 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalConstraintFormat
264 | options:0
265 | metrics:0
266 | views:views]];
267 | }
268 |
269 | #pragma mark - Field Values Management
270 |
271 | - (NSDictionary *)valuesForFields {
272 | NSMutableDictionary *values = [NSMutableDictionary dictionary];
273 |
274 | for (TLFormField *field in [viewFieldMap allValues]) {
275 | id value = [field getValue];
276 | if (value)
277 | [values setObject:value forKey:field.fieldName];
278 | }
279 |
280 | return values;
281 | }
282 |
283 | - (void)updateFieldsVisibility {
284 | for (TLFormField *field in fieldsWithVisibilityPredicate) {
285 | BOOL isVisible = [field.visibilityPredicate evaluateWithObject:field substitutionVariables:viewFieldMap];
286 | field.hidden = !isVisible;
287 | }
288 | }
289 |
290 | - (void)reloadValues {
291 |
292 | if ([self.formDataSource respondsToSelector:@selector(formView:valueForFieldWithName:)]) {
293 | for (TLFormField *field in [viewFieldMap allValues]) {
294 | id value = [self.formDataSource formView:self valueForFieldWithName:field.fieldName];
295 | [field setValue:value];
296 | }
297 |
298 | [self updateFieldsVisibility];
299 | }
300 | }
301 |
302 | #pragma mark - Keyboard Handling
303 |
304 | - (void)handleKeyboardShow:(NSNotification *)notification {
305 |
306 | //If the keyboard is apprearing for any reason other than our fields ignore it
307 | if (!selectedField)
308 | return;
309 |
310 | NSDictionary *info = [notification userInfo];
311 | CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
312 |
313 | if (UIEdgeInsetsEqualToEdgeInsets(defaultInsets, UIEdgeInsetsZero))
314 | defaultInsets = self.contentInset;
315 |
316 | UIEdgeInsets contentInsets = self.contentInset;
317 | contentInsets.bottom = keyboardSize.height;
318 |
319 | self.contentInset = contentInsets;
320 | self.scrollIndicatorInsets = contentInsets;
321 |
322 | CGRect aRect = self.frame;
323 | aRect.size.height -= keyboardSize.height;
324 |
325 | CGRect fieldFrame = selectedField.frame;
326 | CGPoint fieldBottomRight = CGPointMake(fieldFrame.origin.x + fieldFrame.size.width, fieldFrame.origin.y + fieldFrame.size.height);
327 |
328 | if (!CGRectContainsPoint(aRect, fieldBottomRight))
329 | [self scrollRectToVisible:fieldFrame animated:NO];
330 | }
331 |
332 | - (void)handleKeyboardHide:(NSNotification *)notification {
333 | self.contentInset = defaultInsets;
334 | self.scrollIndicatorInsets = defaultInsets;
335 | }
336 |
337 | - (void)orientationChange:(NSNotification *)notification {
338 | [self endEditing:YES];
339 | }
340 |
341 | @end
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TLFormView
2 |
3 | [](http://cocoadocs.org/docsets/TLFormView)
4 | [](http://cocoadocs.org/docsets/TLFormView)
5 | [](http://cocoadocs.org/docsets/TLFormView)
6 |
7 | TLFormView is _yet another_ form view *truly* universal. This means that the same component support both iPhone and iPad using a mechanism around the [Auto Layout Visual Format] to adjust the layout to the running device.
8 |
9 | Because it doesn't extend ``UITableView`` you are completely free to create anything to use as a form field as long as it extends the base ``TLFormField`` class. It also has some nice features like: conditional visibility using ``NSPredicate``, in-place help for each field with ``UIPopoverControler`` and on-the-fly edit/read-only modes switch among other things.
10 |
11 | There is a serie of blog posts called: _TLFormView: the form of the future_ [part I] and [part II] at the [Tryolabs Blog].
12 |
13 | #### Quick Example
14 |
15 | ```objective-c
16 |
17 | //Declare your data model as an extension of TLFormModel
18 |
19 | @interface UserModel : TLFormModel
20 |
21 | @property (nonatomic, strong) TLFormImage *avatar;
22 | @property (nonatomic, strong) TLFormText *user_name;
23 | @property (nonatomic, strong) TLFormNumber *age;
24 |
25 | @end
26 |
27 | @implementation UserModel @end
28 |
29 | ...
30 |
31 | @implementation SomeViewController
32 |
33 | - (void)viewDidLoad {
34 | [super viewDidLoad];
35 |
36 | FormUserModel *formUserModel = [FormUserModel new];
37 |
38 | //This copy the values of our user model to the form model constructing the correct types using plain C functions
39 | formUserModel.avatar = TLFormImage(userModel.avatar);
40 | formUserModel.user_name = TLFormText(userModel.userName);
41 | formUserModel.age = TLFormNumber(userModel.age);
42 |
43 | //'form' is an instance of TLFormView.
44 | [self.form setFormModel:formUserModel];
45 | self.form.editing = YES;
46 | }
47 |
48 | ...
49 |
50 | - (void)saveAction:(id)sender {
51 | //Read the values entered by the user and save it to disc.
52 | NSDictionary values = @{
53 | @"avatar": formUserModel.avatar,
54 | @"user_name": formUserModel.user_name,
55 | @"age": formUserModel.age
56 | }
57 | [values writeToURL:[self saveUrl] atomically:YES];
58 | }
59 |
60 | ```
61 |
62 | This is how it will look:
63 |
64 |
65 |
66 |
67 |
68 | ## Usage
69 |
70 | There are two ways to setup ``TLFormView``:
71 |
72 | - The short one showed in the example above. Using [TLFormModel](#form-setup) class is simple and easy but is less flexible if you are planning to use it _as is_.
73 | - And the long one. Is more powerfull and give you full control over how the form will behave in his life cycle (e.g., when update the fields, who the values are formated, etc).
74 |
75 | Eventualy you can start using ``TLFormModel`` and extend your sublcass overriding the right methods to gain the level of control that the _long way_ gives you. I first will talk about the traditional setup to also explain how ``TLFormView`` works and then I will discuss ``TLFormModel`` in more detail.
76 |
77 | #### Form Setup: the long way
78 |
79 | There is two basic components: ``TLFormView`` and ``TLFormField``. ``TLFormView`` inherit from UIScrollView and add another data source and delegate to create and handle events form the form. You need to implement three methods of the ``TLFormViewDataSource`` protocol to get the form functional, these are:
80 |
81 | ```objective-c
82 | - (NSArray *)fieldNamesToShowInFormView:(TLFormView *)form;
83 |
84 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName;
85 |
86 | - (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form;
87 | ```
88 |
89 | ``fieldNamesToShowInFormView:`` return an array of strings containing the _field names_ (or ids) used to identify the fields in the form. ``formView:fieldForName:`` creates a field for every field name in the form. A field is any subclass of ``TLFormField`` (that is a sub class of ``UIView``), there is a set of default fields that can be used to fit the 80% of the use case. These are:
90 |
91 | - ``TLFormFieldImage``: for display images form an url or a raw image
92 | - ``TLFormFieldList``: for display a list of things
93 | - ``TLFormFieldMultiLine``: to show long text
94 | - ``TLFormFieldSingleLine``: to show short text, numbers and bool values
95 | - ``TLFormFieldTitle``: to show a short text formatted as a title
96 |
97 | The project has a category over ``TLFormField`` with basic methods for customising the aspect of the fields. It has some methods to configure the title and give access to the ``UIAppearance`` proxy of the internal components.
98 |
99 | Once you give a ``TLFormField`` for each field name to the form you need to define how to layout those fields. To do so you need to implement the third method of the data source ``constraintsFormatForFieldsInForm:`` and return an array of strings containing the the rules in [Auto Layout Visual Format] to place the fields in the screen. When writing the rules you need to reference the fields using the field names you return in the ``fieldNamesToShowInFormView:`` implementation. Here you have the chance to check for the capabilities of the device and adapt your layout changing the rules as you need.
100 |
101 | Here is an example implementation of the three required methods in ``TLFormViewDataSource``:
102 |
103 | ```objective-c
104 | - (NSArray *)fieldNamesToShowInFormView:(TLFormView *)form {
105 | return @[
106 | @"user_name",
107 | @"avatar",
108 | @"age",
109 | ];
110 | }
111 |
112 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
113 |
114 | Class fieldClass;
115 | NSString *title;
116 | id value;
117 |
118 | if ([fieldName isEqualToString:@"user_name"]) {
119 | fieldClass = [TLFormFieldSingleLine class];
120 | title = @"User Name";
121 | value = userModel.name;
122 | }
123 | else if ([fieldName isEqualToString:@"avatar"]) {
124 | fieldClass = [TLFormFieldImage class];
125 | title = @"Avatar";
126 | value = userModel.avatarUrl;
127 | }
128 | else {
129 | fieldClass = [TLFormFieldSingleLine class];
130 | title = @"Age";
131 | value = userModel.age;
132 | }
133 |
134 | return [fieldClass formFieldWithName:fieldName title:title andDefaultValue:value];
135 | }
136 |
137 | - (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form {
138 |
139 | //For iPhone we want a vertical layout like we get on a UITableView
140 |
141 | if (isIPhone) {
142 | return @[
143 | //Place the avatar on the top
144 | @"V:|-[avatar(==230)]-",
145 | @"H:|-[avatar(==420)]-|",
146 |
147 | //Now place all the fields to the bottom
148 | @"V:[avatar]-[user_name(>=44)]-",
149 | @"H:|-[user_name]-|",
150 |
151 | @"V:[age(==user_name)]-|",
152 | @"H:|-[age]-|"
153 | ];
154 |
155 | //For anything else we will place the image on the top left and the rest of the fields to the right
156 | } else {
157 | return @[
158 |
159 | //Place the avatar on the top left
160 | @"V:|-[avatar(==230)]",
161 | @"H:|-[avatar]",
162 |
163 | //Now place all the fields to the right
164 | @"V:|-[user_name(>=44)]",
165 | @"H:|-[avatar]-[user_name]-|",
166 |
167 | @"V:[user_name]-[age(==user_name)]-|",
168 | @"H:|-[avatar(==420)]-[age]-|"
169 | ];
170 | }
171 | }
172 | ```
173 |
174 | This example produce this on iPhone
175 |
176 |
177 |
178 |
179 |
180 | and this on iPad
181 |
182 |
183 |
184 |
185 |
186 | ###### Conditional Visibility
187 |
188 | There are cases when you need to show a particular field only when other fields has a special value. For this propose ``TLFormField`` has a property ``visibilityPredicate`` that you can set to an ``NSPredicate`` at the time the field is created. The predicate can access the values of all the fields in the form through the [NSPredicate variable substitution]. This means that in you can access any field value in the form with the syntax ``$[name of the field].value``.
189 |
190 | Let suppose that we want to show the "User Name" field only if the "Age" is greater than 12 years old. We can write a predicate that evaluate to true if the "age" field is greater than 12 and assign it as the ``visibilityPredicate`` for the "user_name" field. This is the code:
191 |
192 | ```objective-c
193 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
194 | ...
195 | //Asume that 'field' is an instance of TLFormField created before somehow
196 | if ([fieldName isEqualToString:@"user_name"])
197 | field.visibilityPredicate = [NSPredicate predicateWithFormat:@"$age.value > 12"];
198 | ...
199 | }
200 | ```
201 |
202 | ###### In-place Help
203 |
204 | Suppose that we want to explain the strange behaviour of our previous example in a concise way. To explain this kind of little special behaviours we can use the in-place support provided by ``TLFormField``. The only thing we need to do is set the help text string in the ``helpText`` property in the field. When there is a value for that property the field show a question mark icon next to the field title that when is taped show the help text in a popup window. Here is the code:
205 |
206 | ```objective-c
207 | - (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
208 | ...
209 | if ([fieldName isEqualToString:@"age"])
210 | field.helpText = @"This is the age of the user. Users with less than 12 years are not allowed to enter his name.";
211 | ...
212 | }
213 | ```
214 |
215 | And this is how it looks when the icon is taped:
216 |
217 |
218 |
219 |
220 |
221 | #### TLFormModel: the short way
222 |
223 | To help you with the setup of the form there is a class ``TLFormModel`` that do what we just did automatically inferring the implementation of the ``TLFormViewDataSource`` form his own taxonomy. You only need to extend it and add one property for each field you want to show in the form. The types of the properties must be one of the types declared in the file TLFormModel.h. Each type corresponds to one of the standard ``TLFormField`` provided.
224 |
225 | To read the values from the form just access the properties like you wold do on any object. To write the values you will need to wrap the values in one of the value constructors provide for each type. Once the values in the model are updated you need to manually perform a reload to show the new values with the ``reloadValues`` method of ``TLFormView`` (this is a cheep update, no view destruction is involved).
226 |
227 | These are the declarations for the types supported by ``TLFormModel``:
228 | - ``TLFormSeparator``: extend NSObject. Rendered as a separator in the form, allow to group fields in sections. Has no value or title.
229 | - ``TLFormText``: extend NSString.
230 | - ``TLFormLongText``: extend NSString.
231 | - ``TLFormTitle``: extend NSString.
232 | - ``TLFormNumber``: extend NSNumber.
233 | - ``TLFormBoolean``: extend NSNumber.
234 | - ``TLFormList``: extend NSArray.
235 | - ``TLFormEnumerated``: extend NSDictionary.
236 | - ``TLFormImage``: extend NSObject.
237 |
238 | These classes doesn't have any logic other than the one inherited form his superclasses, they act almost as an annotation over a property. The intended way to construct values of this types is with plain C functions declared in the TLFormModel.h that check the type of the parameter and copy the value given as parameter.
239 |
240 | Using the type and value of his properties ``TLFormModel`` infer what kind of form field should use for each property. For the field title the property name is used. If the property name use snake-case naming convention each "_" is translated to a space and all words are capitalised, so the property "user_name" will have the title "User Name". The order of the fields is the one used to declare the properties. For ex:
241 |
242 | ```objective-c
243 | @interface UserModel : TLFormModel
244 |
245 | @property (nonatomic, strong) TLFormImage *avatar;
246 | @property (nonatomic, strong) TLFormText *user_name;
247 |
248 | @end
249 | ```
250 |
251 | Will present the avatar field at the top and the user name below.
252 |
253 | Our previous example could be written with ``TLFormModel`` like this:
254 |
255 | ```objective-c
256 | @interface UserModel : TLFormModel
257 |
258 | @property (nonatomic, strong) TLFormImage *avatar;
259 | @property (nonatomic, strong) TLFormText *user_name;
260 | @property (nonatomic, strong) TLFormNumber *age;
261 |
262 | @end
263 |
264 | @implementation UserModel @end
265 | ```
266 |
267 | That's it. This will produce a vertical layout like the one we get on iPhone on all the platforms. Now to connect the ``TLFormModel`` to the form we need to use the ``setFormModel:`` method on the ``TLFormView`` we are using.
268 |
269 | ```objective-c
270 | ...
271 |
272 | //At some point in some place...
273 | FormUserModel *formUserModel = [FormUserModel new];
274 |
275 | //This copy the values of our user model to the form model constructing the correct types using plain C functions
276 | formUserModel.avatar = TLFormImage(userModel.avatar);
277 | formUserModel.user_name = TLFormText(userModel.userName);
278 | formUserModel.age = TLFormNumber(userModel.age);
279 |
280 | TLFormView *form = ...
281 | [form setFormModel:formUserModel];
282 | ```
283 |
284 | To adapt the layout for other device families we need to override the implementation of ``constraintsFormatForFieldsInForm:`` provided by ``TLFormModel`` like this:
285 |
286 | ```objective-c
287 | @implementation UserModel
288 |
289 | - (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form {
290 |
291 | //For iPhone we want the default implementation provided so just return the 'super' version
292 | if (isIPhone)
293 | return [super constraintsFormatForFieldsInForm:form];
294 |
295 | //For anything else use our custom layout
296 | else {
297 | return @[
298 |
299 | //Place the avatar on the top left
300 | @"V:|-[avatar(==230)]",
301 | @"H:|-[avatar]",
302 |
303 | //Now place all the fields to the right
304 | @"V:|-[user_name(>=44)]",
305 | @"H:|-[avatar]-[user_name]-|",
306 |
307 | @"V:[user_name]-[age(==user_name)]-|",
308 | @"H:|-[avatar(==420)]-[age]-|"
309 | ];
310 | }
311 | }
312 |
313 | @end
314 | ```
315 |
316 | Now suppose that we want to edit this user info and get the result back. We need to toggle the edit state on the form and read the updated values from the ``TLFormModel`` instance we are using. This is how we could do it:
317 |
318 | ```objective-c
319 |
320 | - (IBAction)toggleEditionAction:(id)sender {
321 | //Set the form on edit mode
322 | self.form.editing = !self.form.editing;
323 | //update the fields to reflect it
324 | [self.form setupFields];
325 | }
326 |
327 | - (IBAction)saveUserAction:(id)sender {
328 | //Read the values entered by the user and save it to disc.
329 |
330 | NSDictionary values = @{
331 | @"avatar": formUserModel.avatar,
332 | @"user_name": formUserModel.name,
333 | @"age": formUserModel.age
334 | }
335 | [values writeToURL:[self saveUrl] atomically:YES];
336 | }
337 |
338 | ```
339 |
340 | About "editing" the images. The default ``TLFormFieldImage`` don't provide any way to pick an image at this point. You will need to handle the field selection and show some kind of image picker.
341 |
342 | #### Event handling
343 |
344 | ``TLFormView`` report two events through ``TLFormViewDelegate`` out of the box: ``didSelectField:`` and ``didChangeValueForField:``. The events are fired by a ``TLFormField`` implementation that notify a form about it and the form then propagate the event out. Depending on how the field is implemented it might have more events to report. For example the ``TLFormFieldList`` use a ``UITableView`` to present the list of values so it has it's own delegate that allows to customise if the rows can be rearranged, or the selection of one row.
345 |
346 | ## Requirements
347 |
348 | iOS >= 8.0
349 |
350 | ## Installation
351 |
352 | TLFormView is available through [CocoaPods](http://cocoapods.org). To install
353 | it, simply add the following line to your Podfile:
354 |
355 | pod "TLFormView"
356 |
357 | Or clone the repo and check the code under `Pod/Classes`.
358 |
359 | ## Todo
360 |
361 | There are many things that need improvement, here are some:
362 |
363 | - [ ] Centralised style using the methods on ``TLFormField+UIAppearance.h``
364 | - [ ] Expose the ‘defaultMetrics’ as part of the style
365 | - [ ] TLFormModel: look a better way to make objects mutations more efficient
366 | - [ ] Add keyboard next/prev buttons
367 | - [ ] Validate with predicate
368 | - [ ] Refactor the TLFormView to TLFormViewController so we can automatically handle: choose fotos, add a string to a list, layout for orientation, etc.
369 |
370 | If you want to contribute to the project please consider pick one of this items.
371 |
372 | ## Author
373 |
374 | BrunoBerisso, bruno@tryolabs.com
375 |
376 | ## License
377 |
378 | TLFormView is available under the MIT license. See the LICENSE file for more info.
379 |
380 | [Auto Layout Visual Format]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html#//apple_ref/doc/uid/TP40010853-CH3-SW11
381 | [NSPredicate variable substitution]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html
382 | [part I]: http://blog.tryolabs.com/2015/05/14/tlformview-the-form-of-the-future-is-here-part-i/
383 | [part II]: http://blog.tryolabs.com/2015/05/20/tlformview-the-form-of-the-future-is-here-part-ii/
384 | [Tryolabs Blog]: http://blog.tryolabs.com/
385 |
--------------------------------------------------------------------------------
/Example/TLFormView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 9B5B54FF1AC4F0D900604DE2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B5B54FE1AC4F0D900604DE2 /* MobileCoreServices.framework */; };
11 | 9BE9B0EE1B02562E009DA0BD /* TLFormFieldDateTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE9B0ED1B02562E009DA0BD /* TLFormFieldDateTime.m */; };
12 | 9BE9B0F11B03A81B009DA0BD /* TLFormFieldNumeric.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE9B0F01B03A81B009DA0BD /* TLFormFieldNumeric.m */; };
13 | 9BE9B0F51B03BD13009DA0BD /* NSNumber+NumberType.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE9B0F41B03BD13009DA0BD /* NSNumber+NumberType.m */; };
14 | 9BE9B0F81B03C24A009DA0BD /* TLFormFieldYesNo.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE9B0F71B03C24A009DA0BD /* TLFormFieldYesNo.m */; };
15 | 9BE9B0FB1B03D257009DA0BD /* TLFormFieldSelect.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE9B0FA1B03D257009DA0BD /* TLFormFieldSelect.m */; };
16 | 9BECE1881A9B5B4D00CE2FD5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1871A9B5B4D00CE2FD5 /* main.m */; };
17 | 9BECE18B1A9B5B4D00CE2FD5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE18A1A9B5B4D00CE2FD5 /* AppDelegate.m */; };
18 | 9BECE18E1A9B5B4D00CE2FD5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE18D1A9B5B4D00CE2FD5 /* ViewController.m */; };
19 | 9BECE1911A9B5B4D00CE2FD5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BECE18F1A9B5B4D00CE2FD5 /* Main.storyboard */; };
20 | 9BECE1931A9B5B4D00CE2FD5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9BECE1921A9B5B4D00CE2FD5 /* Images.xcassets */; };
21 | 9BECE1961A9B5B4D00CE2FD5 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9BECE1941A9B5B4D00CE2FD5 /* LaunchScreen.xib */; };
22 | 9BECE1B01A9B5B9800CE2FD5 /* TLFormView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1AE1A9B5B9800CE2FD5 /* TLFormView.m */; };
23 | 9BECE1B41A9CAD7800CE2FD5 /* TLFormField.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1B31A9CAD7800CE2FD5 /* TLFormField.m */; };
24 | 9BECE1B81A9CB6DD00CE2FD5 /* TLFormFieldList.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1B71A9CB6DD00CE2FD5 /* TLFormFieldList.m */; };
25 | 9BECE1BB1A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1BA1A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.m */; };
26 | 9BECE1BE1A9CBAEC00CE2FD5 /* TLFormFieldSingleLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1BD1A9CBAEB00CE2FD5 /* TLFormFieldSingleLine.m */; };
27 | 9BECE1C11A9CBB8B00CE2FD5 /* TLFormFieldTitle.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1C01A9CBB8B00CE2FD5 /* TLFormFieldTitle.m */; };
28 | 9BECE1C41A9CBBD100CE2FD5 /* TLFormFieldImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1C31A9CBBD100CE2FD5 /* TLFormFieldImage.m */; };
29 | 9BECE1CA1A9CC4FE00CE2FD5 /* TLFormModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1C91A9CC4FE00CE2FD5 /* TLFormModel.m */; };
30 | 9BECE1D21A9F899500CE2FD5 /* TLFormField+UIAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BECE1D11A9F899500CE2FD5 /* TLFormField+UIAppearance.m */; };
31 | /* End PBXBuildFile section */
32 |
33 | /* Begin PBXFileReference section */
34 | 9B5B54FE1AC4F0D900604DE2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
35 | 9BE9B0EC1B02562E009DA0BD /* TLFormFieldDateTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldDateTime.h; sourceTree = ""; };
36 | 9BE9B0ED1B02562E009DA0BD /* TLFormFieldDateTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLFormFieldDateTime.m; sourceTree = ""; };
37 | 9BE9B0EF1B03A81B009DA0BD /* TLFormFieldNumeric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldNumeric.h; sourceTree = ""; };
38 | 9BE9B0F01B03A81B009DA0BD /* TLFormFieldNumeric.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLFormFieldNumeric.m; sourceTree = ""; };
39 | 9BE9B0F31B03BD13009DA0BD /* NSNumber+NumberType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNumber+NumberType.h"; sourceTree = ""; };
40 | 9BE9B0F41B03BD13009DA0BD /* NSNumber+NumberType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNumber+NumberType.m"; sourceTree = ""; };
41 | 9BE9B0F61B03C24A009DA0BD /* TLFormFieldYesNo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldYesNo.h; sourceTree = ""; };
42 | 9BE9B0F71B03C24A009DA0BD /* TLFormFieldYesNo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLFormFieldYesNo.m; sourceTree = ""; };
43 | 9BE9B0F91B03D257009DA0BD /* TLFormFieldSelect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldSelect.h; sourceTree = ""; };
44 | 9BE9B0FA1B03D257009DA0BD /* TLFormFieldSelect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLFormFieldSelect.m; sourceTree = ""; };
45 | 9BECE1821A9B5B4D00CE2FD5 /* TLFormView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TLFormView.app; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 9BECE1861A9B5B4D00CE2FD5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
47 | 9BECE1871A9B5B4D00CE2FD5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
48 | 9BECE1891A9B5B4D00CE2FD5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
49 | 9BECE18A1A9B5B4D00CE2FD5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
50 | 9BECE18C1A9B5B4D00CE2FD5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
51 | 9BECE18D1A9B5B4D00CE2FD5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
52 | 9BECE1901A9B5B4D00CE2FD5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
53 | 9BECE1921A9B5B4D00CE2FD5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
54 | 9BECE1951A9B5B4D00CE2FD5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
55 | 9BECE1AD1A9B5B9800CE2FD5 /* TLFormView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = TLFormView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
56 | 9BECE1AE1A9B5B9800CE2FD5 /* TLFormView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormView.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
57 | 9BECE1B21A9CAD7800CE2FD5 /* TLFormField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormField.h; sourceTree = ""; };
58 | 9BECE1B31A9CAD7800CE2FD5 /* TLFormField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormField.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
59 | 9BECE1B51A9CB4F200CE2FD5 /* TLFormField+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TLFormField+Protected.h"; sourceTree = ""; };
60 | 9BECE1B61A9CB6DD00CE2FD5 /* TLFormFieldList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldList.h; sourceTree = ""; };
61 | 9BECE1B71A9CB6DD00CE2FD5 /* TLFormFieldList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormFieldList.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
62 | 9BECE1B91A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = TLFormFieldMultiLine.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
63 | 9BECE1BA1A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormFieldMultiLine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
64 | 9BECE1BC1A9CBAEB00CE2FD5 /* TLFormFieldSingleLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldSingleLine.h; sourceTree = ""; };
65 | 9BECE1BD1A9CBAEB00CE2FD5 /* TLFormFieldSingleLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormFieldSingleLine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
66 | 9BECE1BF1A9CBB8B00CE2FD5 /* TLFormFieldTitle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldTitle.h; sourceTree = ""; };
67 | 9BECE1C01A9CBB8B00CE2FD5 /* TLFormFieldTitle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormFieldTitle.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
68 | 9BECE1C21A9CBBD100CE2FD5 /* TLFormFieldImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormFieldImage.h; sourceTree = ""; };
69 | 9BECE1C31A9CBBD100CE2FD5 /* TLFormFieldImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormFieldImage.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
70 | 9BECE1C81A9CC4FE00CE2FD5 /* TLFormModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLFormModel.h; sourceTree = ""; };
71 | 9BECE1C91A9CC4FE00CE2FD5 /* TLFormModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = TLFormModel.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
72 | 9BECE1D01A9F899500CE2FD5 /* TLFormField+UIAppearance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TLFormField+UIAppearance.h"; sourceTree = ""; };
73 | 9BECE1D11A9F899500CE2FD5 /* TLFormField+UIAppearance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "TLFormField+UIAppearance.m"; sourceTree = ""; };
74 | 9BECE1D31A9F89E200CE2FD5 /* TLFormAllFields.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TLFormAllFields.h; sourceTree = ""; };
75 | /* End PBXFileReference section */
76 |
77 | /* Begin PBXFrameworksBuildPhase section */
78 | 9BECE17F1A9B5B4D00CE2FD5 /* Frameworks */ = {
79 | isa = PBXFrameworksBuildPhase;
80 | buildActionMask = 2147483647;
81 | files = (
82 | 9B5B54FF1AC4F0D900604DE2 /* MobileCoreServices.framework in Frameworks */,
83 | );
84 | runOnlyForDeploymentPostprocessing = 0;
85 | };
86 | /* End PBXFrameworksBuildPhase section */
87 |
88 | /* Begin PBXGroup section */
89 | 9BE9B0F21B03BCF3009DA0BD /* Extensions */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 9BE9B0F31B03BD13009DA0BD /* NSNumber+NumberType.h */,
93 | 9BE9B0F41B03BD13009DA0BD /* NSNumber+NumberType.m */,
94 | );
95 | path = Extensions;
96 | sourceTree = "";
97 | };
98 | 9BECE1791A9B5B4D00CE2FD5 = {
99 | isa = PBXGroup;
100 | children = (
101 | 9B5B54FE1AC4F0D900604DE2 /* MobileCoreServices.framework */,
102 | 9BECE1841A9B5B4D00CE2FD5 /* TLFormView */,
103 | 9BECE1831A9B5B4D00CE2FD5 /* Products */,
104 | );
105 | sourceTree = "";
106 | };
107 | 9BECE1831A9B5B4D00CE2FD5 /* Products */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 9BECE1821A9B5B4D00CE2FD5 /* TLFormView.app */,
111 | );
112 | name = Products;
113 | sourceTree = "";
114 | };
115 | 9BECE1841A9B5B4D00CE2FD5 /* TLFormView */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 9BECE1AB1A9B5B9800CE2FD5 /* Classes */,
119 | 9BECE1891A9B5B4D00CE2FD5 /* AppDelegate.h */,
120 | 9BECE18A1A9B5B4D00CE2FD5 /* AppDelegate.m */,
121 | 9BECE18C1A9B5B4D00CE2FD5 /* ViewController.h */,
122 | 9BECE18D1A9B5B4D00CE2FD5 /* ViewController.m */,
123 | 9BECE18F1A9B5B4D00CE2FD5 /* Main.storyboard */,
124 | 9BECE1921A9B5B4D00CE2FD5 /* Images.xcassets */,
125 | 9BECE1941A9B5B4D00CE2FD5 /* LaunchScreen.xib */,
126 | 9BECE1851A9B5B4D00CE2FD5 /* Supporting Files */,
127 | );
128 | path = TLFormView;
129 | sourceTree = "";
130 | };
131 | 9BECE1851A9B5B4D00CE2FD5 /* Supporting Files */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 9BECE1861A9B5B4D00CE2FD5 /* Info.plist */,
135 | 9BECE1871A9B5B4D00CE2FD5 /* main.m */,
136 | );
137 | name = "Supporting Files";
138 | sourceTree = "";
139 | };
140 | 9BECE1AB1A9B5B9800CE2FD5 /* Classes */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 9BE9B0F21B03BCF3009DA0BD /* Extensions */,
144 | 9BECE1CF1A9F898900CE2FD5 /* Style */,
145 | 9BECE1B11A9CACEA00CE2FD5 /* Fields */,
146 | 9BECE1AD1A9B5B9800CE2FD5 /* TLFormView.h */,
147 | 9BECE1AE1A9B5B9800CE2FD5 /* TLFormView.m */,
148 | 9BECE1C81A9CC4FE00CE2FD5 /* TLFormModel.h */,
149 | 9BECE1C91A9CC4FE00CE2FD5 /* TLFormModel.m */,
150 | );
151 | name = Classes;
152 | path = ../../Pod/Classes;
153 | sourceTree = "";
154 | };
155 | 9BECE1B11A9CACEA00CE2FD5 /* Fields */ = {
156 | isa = PBXGroup;
157 | children = (
158 | 9BECE1B21A9CAD7800CE2FD5 /* TLFormField.h */,
159 | 9BECE1B31A9CAD7800CE2FD5 /* TLFormField.m */,
160 | 9BECE1B51A9CB4F200CE2FD5 /* TLFormField+Protected.h */,
161 | 9BECE1B61A9CB6DD00CE2FD5 /* TLFormFieldList.h */,
162 | 9BECE1B71A9CB6DD00CE2FD5 /* TLFormFieldList.m */,
163 | 9BECE1B91A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.h */,
164 | 9BECE1BA1A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.m */,
165 | 9BECE1BC1A9CBAEB00CE2FD5 /* TLFormFieldSingleLine.h */,
166 | 9BECE1BD1A9CBAEB00CE2FD5 /* TLFormFieldSingleLine.m */,
167 | 9BECE1BF1A9CBB8B00CE2FD5 /* TLFormFieldTitle.h */,
168 | 9BECE1C01A9CBB8B00CE2FD5 /* TLFormFieldTitle.m */,
169 | 9BECE1C21A9CBBD100CE2FD5 /* TLFormFieldImage.h */,
170 | 9BECE1C31A9CBBD100CE2FD5 /* TLFormFieldImage.m */,
171 | 9BECE1D31A9F89E200CE2FD5 /* TLFormAllFields.h */,
172 | 9BE9B0EC1B02562E009DA0BD /* TLFormFieldDateTime.h */,
173 | 9BE9B0ED1B02562E009DA0BD /* TLFormFieldDateTime.m */,
174 | 9BE9B0EF1B03A81B009DA0BD /* TLFormFieldNumeric.h */,
175 | 9BE9B0F01B03A81B009DA0BD /* TLFormFieldNumeric.m */,
176 | 9BE9B0F61B03C24A009DA0BD /* TLFormFieldYesNo.h */,
177 | 9BE9B0F71B03C24A009DA0BD /* TLFormFieldYesNo.m */,
178 | 9BE9B0F91B03D257009DA0BD /* TLFormFieldSelect.h */,
179 | 9BE9B0FA1B03D257009DA0BD /* TLFormFieldSelect.m */,
180 | );
181 | path = Fields;
182 | sourceTree = "";
183 | };
184 | 9BECE1CF1A9F898900CE2FD5 /* Style */ = {
185 | isa = PBXGroup;
186 | children = (
187 | 9BECE1D01A9F899500CE2FD5 /* TLFormField+UIAppearance.h */,
188 | 9BECE1D11A9F899500CE2FD5 /* TLFormField+UIAppearance.m */,
189 | );
190 | path = Style;
191 | sourceTree = "";
192 | };
193 | /* End PBXGroup section */
194 |
195 | /* Begin PBXNativeTarget section */
196 | 9BECE1811A9B5B4D00CE2FD5 /* TLFormView */ = {
197 | isa = PBXNativeTarget;
198 | buildConfigurationList = 9BECE1A51A9B5B4E00CE2FD5 /* Build configuration list for PBXNativeTarget "TLFormView" */;
199 | buildPhases = (
200 | 9BECE17E1A9B5B4D00CE2FD5 /* Sources */,
201 | 9BECE17F1A9B5B4D00CE2FD5 /* Frameworks */,
202 | 9BECE1801A9B5B4D00CE2FD5 /* Resources */,
203 | );
204 | buildRules = (
205 | );
206 | dependencies = (
207 | );
208 | name = TLFormView;
209 | productName = TLFormView;
210 | productReference = 9BECE1821A9B5B4D00CE2FD5 /* TLFormView.app */;
211 | productType = "com.apple.product-type.application";
212 | };
213 | /* End PBXNativeTarget section */
214 |
215 | /* Begin PBXProject section */
216 | 9BECE17A1A9B5B4D00CE2FD5 /* Project object */ = {
217 | isa = PBXProject;
218 | attributes = {
219 | LastUpgradeCheck = 0610;
220 | ORGANIZATIONNAME = "Bruno Berisso";
221 | TargetAttributes = {
222 | 9BECE1811A9B5B4D00CE2FD5 = {
223 | CreatedOnToolsVersion = 6.1.1;
224 | };
225 | };
226 | };
227 | buildConfigurationList = 9BECE17D1A9B5B4D00CE2FD5 /* Build configuration list for PBXProject "TLFormView" */;
228 | compatibilityVersion = "Xcode 3.2";
229 | developmentRegion = English;
230 | hasScannedForEncodings = 0;
231 | knownRegions = (
232 | en,
233 | Base,
234 | );
235 | mainGroup = 9BECE1791A9B5B4D00CE2FD5;
236 | productRefGroup = 9BECE1831A9B5B4D00CE2FD5 /* Products */;
237 | projectDirPath = "";
238 | projectRoot = "";
239 | targets = (
240 | 9BECE1811A9B5B4D00CE2FD5 /* TLFormView */,
241 | );
242 | };
243 | /* End PBXProject section */
244 |
245 | /* Begin PBXResourcesBuildPhase section */
246 | 9BECE1801A9B5B4D00CE2FD5 /* Resources */ = {
247 | isa = PBXResourcesBuildPhase;
248 | buildActionMask = 2147483647;
249 | files = (
250 | 9BECE1911A9B5B4D00CE2FD5 /* Main.storyboard in Resources */,
251 | 9BECE1961A9B5B4D00CE2FD5 /* LaunchScreen.xib in Resources */,
252 | 9BECE1931A9B5B4D00CE2FD5 /* Images.xcassets in Resources */,
253 | );
254 | runOnlyForDeploymentPostprocessing = 0;
255 | };
256 | /* End PBXResourcesBuildPhase section */
257 |
258 | /* Begin PBXSourcesBuildPhase section */
259 | 9BECE17E1A9B5B4D00CE2FD5 /* Sources */ = {
260 | isa = PBXSourcesBuildPhase;
261 | buildActionMask = 2147483647;
262 | files = (
263 | 9BECE18E1A9B5B4D00CE2FD5 /* ViewController.m in Sources */,
264 | 9BE9B0FB1B03D257009DA0BD /* TLFormFieldSelect.m in Sources */,
265 | 9BECE1D21A9F899500CE2FD5 /* TLFormField+UIAppearance.m in Sources */,
266 | 9BE9B0F11B03A81B009DA0BD /* TLFormFieldNumeric.m in Sources */,
267 | 9BE9B0F81B03C24A009DA0BD /* TLFormFieldYesNo.m in Sources */,
268 | 9BECE18B1A9B5B4D00CE2FD5 /* AppDelegate.m in Sources */,
269 | 9BECE1B81A9CB6DD00CE2FD5 /* TLFormFieldList.m in Sources */,
270 | 9BECE1B01A9B5B9800CE2FD5 /* TLFormView.m in Sources */,
271 | 9BE9B0F51B03BD13009DA0BD /* NSNumber+NumberType.m in Sources */,
272 | 9BECE1C41A9CBBD100CE2FD5 /* TLFormFieldImage.m in Sources */,
273 | 9BE9B0EE1B02562E009DA0BD /* TLFormFieldDateTime.m in Sources */,
274 | 9BECE1BE1A9CBAEC00CE2FD5 /* TLFormFieldSingleLine.m in Sources */,
275 | 9BECE1881A9B5B4D00CE2FD5 /* main.m in Sources */,
276 | 9BECE1C11A9CBB8B00CE2FD5 /* TLFormFieldTitle.m in Sources */,
277 | 9BECE1CA1A9CC4FE00CE2FD5 /* TLFormModel.m in Sources */,
278 | 9BECE1B41A9CAD7800CE2FD5 /* TLFormField.m in Sources */,
279 | 9BECE1BB1A9CBA3F00CE2FD5 /* TLFormFieldMultiLine.m in Sources */,
280 | );
281 | runOnlyForDeploymentPostprocessing = 0;
282 | };
283 | /* End PBXSourcesBuildPhase section */
284 |
285 | /* Begin PBXVariantGroup section */
286 | 9BECE18F1A9B5B4D00CE2FD5 /* Main.storyboard */ = {
287 | isa = PBXVariantGroup;
288 | children = (
289 | 9BECE1901A9B5B4D00CE2FD5 /* Base */,
290 | );
291 | name = Main.storyboard;
292 | sourceTree = "";
293 | };
294 | 9BECE1941A9B5B4D00CE2FD5 /* LaunchScreen.xib */ = {
295 | isa = PBXVariantGroup;
296 | children = (
297 | 9BECE1951A9B5B4D00CE2FD5 /* Base */,
298 | );
299 | name = LaunchScreen.xib;
300 | sourceTree = "";
301 | };
302 | /* End PBXVariantGroup section */
303 |
304 | /* Begin XCBuildConfiguration section */
305 | 9BECE1A31A9B5B4E00CE2FD5 /* Debug */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
310 | CLANG_CXX_LIBRARY = "libc++";
311 | CLANG_ENABLE_MODULES = YES;
312 | CLANG_ENABLE_OBJC_ARC = YES;
313 | CLANG_WARN_BOOL_CONVERSION = YES;
314 | CLANG_WARN_CONSTANT_CONVERSION = YES;
315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
316 | CLANG_WARN_EMPTY_BODY = YES;
317 | CLANG_WARN_ENUM_CONVERSION = YES;
318 | CLANG_WARN_INT_CONVERSION = YES;
319 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
320 | CLANG_WARN_UNREACHABLE_CODE = YES;
321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
323 | COPY_PHASE_STRIP = NO;
324 | ENABLE_STRICT_OBJC_MSGSEND = YES;
325 | GCC_C_LANGUAGE_STANDARD = gnu99;
326 | GCC_DYNAMIC_NO_PIC = NO;
327 | GCC_OPTIMIZATION_LEVEL = 0;
328 | GCC_PREPROCESSOR_DEFINITIONS = (
329 | "DEBUG=1",
330 | "$(inherited)",
331 | );
332 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
333 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
334 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
335 | GCC_WARN_UNDECLARED_SELECTOR = YES;
336 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
337 | GCC_WARN_UNUSED_FUNCTION = YES;
338 | GCC_WARN_UNUSED_VARIABLE = YES;
339 | IPHONEOS_DEPLOYMENT_TARGET = 8.1;
340 | MTL_ENABLE_DEBUG_INFO = YES;
341 | ONLY_ACTIVE_ARCH = YES;
342 | SDKROOT = iphoneos;
343 | TARGETED_DEVICE_FAMILY = "1,2";
344 | };
345 | name = Debug;
346 | };
347 | 9BECE1A41A9B5B4E00CE2FD5 /* Release */ = {
348 | isa = XCBuildConfiguration;
349 | buildSettings = {
350 | ALWAYS_SEARCH_USER_PATHS = NO;
351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
352 | CLANG_CXX_LIBRARY = "libc++";
353 | CLANG_ENABLE_MODULES = YES;
354 | CLANG_ENABLE_OBJC_ARC = YES;
355 | CLANG_WARN_BOOL_CONVERSION = YES;
356 | CLANG_WARN_CONSTANT_CONVERSION = YES;
357 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
358 | CLANG_WARN_EMPTY_BODY = YES;
359 | CLANG_WARN_ENUM_CONVERSION = YES;
360 | CLANG_WARN_INT_CONVERSION = YES;
361 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
362 | CLANG_WARN_UNREACHABLE_CODE = YES;
363 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
364 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
365 | COPY_PHASE_STRIP = YES;
366 | ENABLE_NS_ASSERTIONS = NO;
367 | ENABLE_STRICT_OBJC_MSGSEND = YES;
368 | GCC_C_LANGUAGE_STANDARD = gnu99;
369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
371 | GCC_WARN_UNDECLARED_SELECTOR = YES;
372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
373 | GCC_WARN_UNUSED_FUNCTION = YES;
374 | GCC_WARN_UNUSED_VARIABLE = YES;
375 | IPHONEOS_DEPLOYMENT_TARGET = 8.1;
376 | MTL_ENABLE_DEBUG_INFO = NO;
377 | SDKROOT = iphoneos;
378 | TARGETED_DEVICE_FAMILY = "1,2";
379 | VALIDATE_PRODUCT = YES;
380 | };
381 | name = Release;
382 | };
383 | 9BECE1A61A9B5B4E00CE2FD5 /* Debug */ = {
384 | isa = XCBuildConfiguration;
385 | buildSettings = {
386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
387 | INFOPLIST_FILE = TLFormView/Info.plist;
388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
389 | PRODUCT_NAME = "$(TARGET_NAME)";
390 | };
391 | name = Debug;
392 | };
393 | 9BECE1A71A9B5B4E00CE2FD5 /* Release */ = {
394 | isa = XCBuildConfiguration;
395 | buildSettings = {
396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
397 | INFOPLIST_FILE = TLFormView/Info.plist;
398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
399 | PRODUCT_NAME = "$(TARGET_NAME)";
400 | };
401 | name = Release;
402 | };
403 | /* End XCBuildConfiguration section */
404 |
405 | /* Begin XCConfigurationList section */
406 | 9BECE17D1A9B5B4D00CE2FD5 /* Build configuration list for PBXProject "TLFormView" */ = {
407 | isa = XCConfigurationList;
408 | buildConfigurations = (
409 | 9BECE1A31A9B5B4E00CE2FD5 /* Debug */,
410 | 9BECE1A41A9B5B4E00CE2FD5 /* Release */,
411 | );
412 | defaultConfigurationIsVisible = 0;
413 | defaultConfigurationName = Release;
414 | };
415 | 9BECE1A51A9B5B4E00CE2FD5 /* Build configuration list for PBXNativeTarget "TLFormView" */ = {
416 | isa = XCConfigurationList;
417 | buildConfigurations = (
418 | 9BECE1A61A9B5B4E00CE2FD5 /* Debug */,
419 | 9BECE1A71A9B5B4E00CE2FD5 /* Release */,
420 | );
421 | defaultConfigurationIsVisible = 0;
422 | defaultConfigurationName = Release;
423 | };
424 | /* End XCConfigurationList section */
425 | };
426 | rootObject = 9BECE17A1A9B5B4D00CE2FD5 /* Project object */;
427 | }
428 |
--------------------------------------------------------------------------------