├── .gitignore
├── .travis.yml
├── Example
├── Podfile
├── Podfile.lock
├── Pods
│ ├── Local Podspecs
│ │ └── SetupController.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ └── Target Support Files
│ │ ├── Pods-SetupController_Example
│ │ ├── Info.plist
│ │ ├── Pods-SetupController_Example-acknowledgements.markdown
│ │ ├── Pods-SetupController_Example-acknowledgements.plist
│ │ ├── Pods-SetupController_Example-dummy.m
│ │ ├── Pods-SetupController_Example-frameworks.sh
│ │ ├── Pods-SetupController_Example-resources.sh
│ │ ├── Pods-SetupController_Example-umbrella.h
│ │ ├── Pods-SetupController_Example.debug.xcconfig
│ │ ├── Pods-SetupController_Example.modulemap
│ │ └── Pods-SetupController_Example.release.xcconfig
│ │ ├── Pods-SetupController_Tests
│ │ ├── Info.plist
│ │ ├── Pods-SetupController_Tests-acknowledgements.markdown
│ │ ├── Pods-SetupController_Tests-acknowledgements.plist
│ │ ├── Pods-SetupController_Tests-dummy.m
│ │ ├── Pods-SetupController_Tests-frameworks.sh
│ │ ├── Pods-SetupController_Tests-resources.sh
│ │ ├── Pods-SetupController_Tests-umbrella.h
│ │ ├── Pods-SetupController_Tests.debug.xcconfig
│ │ ├── Pods-SetupController_Tests.modulemap
│ │ └── Pods-SetupController_Tests.release.xcconfig
│ │ └── SetupController
│ │ ├── Info.plist
│ │ ├── SetupController-dummy.m
│ │ ├── SetupController-prefix.pch
│ │ ├── SetupController-umbrella.h
│ │ ├── SetupController.modulemap
│ │ └── SetupController.xcconfig
├── SetupController.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── SetupController-Example.xcscheme
├── SetupController.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── SetupController
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── SampleImage.imageset
│ │ │ ├── Contents.json
│ │ │ └── SampleImage@2x.png
│ ├── MBAccountController.h
│ ├── MBAccountController.m
│ ├── MBAppDelegate.h
│ ├── MBAppDelegate.m
│ ├── MBBackupController.h
│ ├── MBBackupController.m
│ ├── MBProgressController.h
│ ├── MBProgressController.m
│ ├── MBSampleSetupController.h
│ ├── MBSampleSetupController.m
│ ├── MBViewController.h
│ ├── MBViewController.m
│ ├── MBWelcomeController.h
│ ├── MBWelcomeController.m
│ ├── Main.storyboard
│ ├── SetupController-Info.plist
│ ├── SetupController-Prefix.pch
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ └── main.m
└── Tests
│ ├── Tests-Info.plist
│ ├── Tests-Prefix.pch
│ ├── Tests.m
│ └── en.lproj
│ └── InfoPlist.strings
├── Images
├── iPadLandscape.png
└── iPhonePortrait.png
├── LICENSE
├── README.md
├── SetupController.podspec
└── SetupController
├── Assets
└── .gitkeep
└── Classes
├── .gitkeep
├── MBBasePageController.h
├── MBBasePageController.m
├── MBFinishPageController.h
├── MBFinishPageController.m
├── MBLabelCell.h
├── MBLabelCell.m
├── MBLabelItem.h
├── MBLabelItem.m
├── MBProgressPageController.h
├── MBProgressPageController.m
├── MBSectionFooter.h
├── MBSectionFooter.m
├── MBSectionHeader.h
├── MBSectionHeader.m
├── MBSetupController.h
├── MBSetupController.m
├── MBSetupControllerUtilities.h
├── MBSetupControllerUtilities.m
├── MBSetupPageCell.h
├── MBSetupPageCell.m
├── MBSetupPageItem.h
├── MBSetupPageItem.m
├── MBSetupPageSection.h
├── MBSetupPageSection.m
├── MBSwitchCell.h
├── MBSwitchCell.m
├── MBSwitchItem.h
├── MBSwitchItem.m
├── MBTableViewPageController.h
├── MBTableViewPageController.m
├── MBTextFieldCell.h
├── MBTextFieldCell.m
├── MBTextFieldItem.h
└── MBTextFieldItem.m
/.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 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode9.1
3 |
4 | before_install:
5 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet
6 | - pod --version
7 |
8 | script:
9 | - set -o pipefail
10 | - pod lib lint
11 | - xcodebuild clean build -workspace Example/SetupController.xcworkspace -scheme SetupController-Example -sdk iphonesimulator PLATFORM_NAME=iphonesimulator ONLY_ACTIVE_ARCH=NO -configuration Release | xcpretty -c
12 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 | platform :ios, '9.3'
3 |
4 | target 'SetupController_Example' do
5 | pod 'SetupController', :path => '../'
6 |
7 | target 'SetupController_Tests' do
8 | inherit! :search_paths
9 |
10 |
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SetupController (0.4.0)
3 |
4 | DEPENDENCIES:
5 | - SetupController (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | SetupController:
9 | :path: ../
10 |
11 | SPEC CHECKSUMS:
12 | SetupController: 2f20a4d985a594aa65e54cc10137ac92655416d4
13 |
14 | PODFILE CHECKSUM: 9091686d40ef0c6b26386858b00c0e8e8c1d0333
15 |
16 | COCOAPODS: 1.4.0
17 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/SetupController.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SetupController",
3 | "version": "0.4.0",
4 | "summary": "MBSetupController can present a sequence of dialog views that lead the user through a series of steps like a wizard or a setup assistant.",
5 | "description": "A MBSetupController is a subclass of UIViewController controller that acts like a wizard or setup assistant to present a sequence of dialog views that lead the user through a series of steps.",
6 | "homepage": "https://github.com/miximka/SetupController",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "authors": {
12 | "Maksim Bauer": "miximka@gmail.com"
13 | },
14 | "source": {
15 | "git": "https://github.com/miximka/SetupController.git",
16 | "tag": "0.4.0"
17 | },
18 | "social_media_url": "https://twitter.com/miximka",
19 | "platforms": {
20 | "ios": "8.0"
21 | },
22 | "source_files": "SetupController/Classes/**/*"
23 | }
24 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SetupController (0.4.0)
3 |
4 | DEPENDENCIES:
5 | - SetupController (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | SetupController:
9 | :path: ../
10 |
11 | SPEC CHECKSUMS:
12 | SetupController: 2f20a4d985a594aa65e54cc10137ac92655416d4
13 |
14 | PODFILE CHECKSUM: 9091686d40ef0c6b26386858b00c0e8e8c1d0333
15 |
16 | COCOAPODS: 1.4.0
17 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## SetupController
5 |
6 | Copyright (c) 2015 Maksim Bauer
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 | Generated by CocoaPods - https://cocoapods.org
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Copyright (c) 2015 Maksim Bauer <miximka@gmail.com>
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy
20 | of this software and associated documentation files (the "Software"), to deal
21 | in the Software without restriction, including without limitation the rights
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 | copies of the Software, and to permit persons to whom the Software is
24 | furnished to do so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in
27 | all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 | THE SOFTWARE.
36 |
37 | License
38 | MIT
39 | Title
40 | SetupController
41 | Type
42 | PSGroupSpecifier
43 |
44 |
45 | FooterText
46 | Generated by CocoaPods - https://cocoapods.org
47 | Title
48 |
49 | Type
50 | PSGroupSpecifier
51 |
52 |
53 | StringsTable
54 | Acknowledgements
55 | Title
56 | Acknowledgements
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_SetupController_Example : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_SetupController_Example
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | # Used as a return value for each invocation of `strip_invalid_archs` function.
10 | STRIP_BINARY_RETVAL=0
11 |
12 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
13 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
14 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
15 |
16 | # Copies and strips a vendored framework
17 | install_framework()
18 | {
19 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
20 | local source="${BUILT_PRODUCTS_DIR}/$1"
21 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
22 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
23 | elif [ -r "$1" ]; then
24 | local source="$1"
25 | fi
26 |
27 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
28 |
29 | if [ -L "${source}" ]; then
30 | echo "Symlinked..."
31 | source="$(readlink "${source}")"
32 | fi
33 |
34 | # Use filter instead of exclude so missing patterns don't throw errors.
35 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
36 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
37 |
38 | local basename
39 | basename="$(basename -s .framework "$1")"
40 | binary="${destination}/${basename}.framework/${basename}"
41 | if ! [ -r "$binary" ]; then
42 | binary="${destination}/${basename}"
43 | fi
44 |
45 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
46 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
47 | strip_invalid_archs "$binary"
48 | fi
49 |
50 | # Resign the code if required by the build settings to avoid unstable apps
51 | code_sign_if_enabled "${destination}/$(basename "$1")"
52 |
53 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
54 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
55 | local swift_runtime_libs
56 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
57 | for lib in $swift_runtime_libs; do
58 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
59 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
60 | code_sign_if_enabled "${destination}/${lib}"
61 | done
62 | fi
63 | }
64 |
65 | # Copies and strips a vendored dSYM
66 | install_dsym() {
67 | local source="$1"
68 | if [ -r "$source" ]; then
69 | # Copy the dSYM into a the targets temp dir.
70 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
71 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
72 |
73 | local basename
74 | basename="$(basename -s .framework.dSYM "$source")"
75 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
76 |
77 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
78 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
79 | strip_invalid_archs "$binary"
80 | fi
81 |
82 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
83 | # Move the stripped file into its final destination.
84 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
85 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
86 | else
87 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
88 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
89 | fi
90 | fi
91 | }
92 |
93 | # Signs a framework with the provided identity
94 | code_sign_if_enabled() {
95 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
96 | # Use the current code_sign_identitiy
97 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
98 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
99 |
100 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
101 | code_sign_cmd="$code_sign_cmd &"
102 | fi
103 | echo "$code_sign_cmd"
104 | eval "$code_sign_cmd"
105 | fi
106 | }
107 |
108 | # Strip invalid architectures
109 | strip_invalid_archs() {
110 | binary="$1"
111 | # Get architectures for current target binary
112 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
113 | # Intersect them with the architectures we are building for
114 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
115 | # If there are no archs supported by this binary then warn the user
116 | if [[ -z "$intersected_archs" ]]; then
117 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
118 | STRIP_BINARY_RETVAL=0
119 | return
120 | fi
121 | stripped=""
122 | for arch in $binary_archs; do
123 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
124 | # Strip non-valid architectures in-place
125 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
126 | stripped="$stripped $arch"
127 | fi
128 | done
129 | if [[ "$stripped" ]]; then
130 | echo "Stripped $binary of architectures:$stripped"
131 | fi
132 | STRIP_BINARY_RETVAL=1
133 | }
134 |
135 |
136 | if [[ "$CONFIGURATION" == "Debug" ]]; then
137 | install_framework "${BUILT_PRODUCTS_DIR}/SetupController/SetupController.framework"
138 | fi
139 | if [[ "$CONFIGURATION" == "Release" ]]; then
140 | install_framework "${BUILT_PRODUCTS_DIR}/SetupController/SetupController.framework"
141 | fi
142 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
143 | wait
144 | fi
145 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
14 |
15 | case "${TARGETED_DEVICE_FAMILY}" in
16 | 1,2)
17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
18 | ;;
19 | 1)
20 | TARGET_DEVICE_ARGS="--target-device iphone"
21 | ;;
22 | 2)
23 | TARGET_DEVICE_ARGS="--target-device ipad"
24 | ;;
25 | 3)
26 | TARGET_DEVICE_ARGS="--target-device tv"
27 | ;;
28 | 4)
29 | TARGET_DEVICE_ARGS="--target-device watch"
30 | ;;
31 | *)
32 | TARGET_DEVICE_ARGS="--target-device mac"
33 | ;;
34 | esac
35 |
36 | install_resource()
37 | {
38 | if [[ "$1" = /* ]] ; then
39 | RESOURCE_PATH="$1"
40 | else
41 | RESOURCE_PATH="${PODS_ROOT}/$1"
42 | fi
43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
44 | cat << EOM
45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
46 | EOM
47 | exit 1
48 | fi
49 | case $RESOURCE_PATH in
50 | *.storyboard)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.xib)
55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
57 | ;;
58 | *.framework)
59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
63 | ;;
64 | *.xcdatamodel)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
67 | ;;
68 | *.xcdatamodeld)
69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
71 | ;;
72 | *.xcmappingmodel)
73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
75 | ;;
76 | *.xcassets)
77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
79 | ;;
80 | *)
81 | echo "$RESOURCE_PATH" || true
82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
83 | ;;
84 | esac
85 | }
86 |
87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
92 | fi
93 | rm -f "$RESOURCES_TO_COPY"
94 |
95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
96 | then
97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
99 | while read line; do
100 | if [[ $line != "${PODS_ROOT}*" ]]; then
101 | XCASSET_FILES+=("$line")
102 | fi
103 | done <<<"$OTHER_XCASSETS"
104 |
105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
106 | fi
107 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_SetupController_ExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_SetupController_ExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SetupController"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SetupController/SetupController.framework/Headers"
5 | OTHER_LDFLAGS = $(inherited) -framework "SetupController"
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
9 | PODS_ROOT = ${SRCROOT}/Pods
10 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_SetupController_Example {
2 | umbrella header "Pods-SetupController_Example-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Example/Pods-SetupController_Example.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SetupController"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SetupController/SetupController.framework/Headers"
5 | OTHER_LDFLAGS = $(inherited) -framework "SetupController"
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
9 | PODS_ROOT = ${SRCROOT}/Pods
10 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_SetupController_Tests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_SetupController_Tests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | # Used as a return value for each invocation of `strip_invalid_archs` function.
10 | STRIP_BINARY_RETVAL=0
11 |
12 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
13 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
14 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
15 |
16 | # Copies and strips a vendored framework
17 | install_framework()
18 | {
19 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
20 | local source="${BUILT_PRODUCTS_DIR}/$1"
21 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
22 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
23 | elif [ -r "$1" ]; then
24 | local source="$1"
25 | fi
26 |
27 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
28 |
29 | if [ -L "${source}" ]; then
30 | echo "Symlinked..."
31 | source="$(readlink "${source}")"
32 | fi
33 |
34 | # Use filter instead of exclude so missing patterns don't throw errors.
35 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
36 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
37 |
38 | local basename
39 | basename="$(basename -s .framework "$1")"
40 | binary="${destination}/${basename}.framework/${basename}"
41 | if ! [ -r "$binary" ]; then
42 | binary="${destination}/${basename}"
43 | fi
44 |
45 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
46 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
47 | strip_invalid_archs "$binary"
48 | fi
49 |
50 | # Resign the code if required by the build settings to avoid unstable apps
51 | code_sign_if_enabled "${destination}/$(basename "$1")"
52 |
53 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
54 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
55 | local swift_runtime_libs
56 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
57 | for lib in $swift_runtime_libs; do
58 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
59 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
60 | code_sign_if_enabled "${destination}/${lib}"
61 | done
62 | fi
63 | }
64 |
65 | # Copies and strips a vendored dSYM
66 | install_dsym() {
67 | local source="$1"
68 | if [ -r "$source" ]; then
69 | # Copy the dSYM into a the targets temp dir.
70 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
71 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
72 |
73 | local basename
74 | basename="$(basename -s .framework.dSYM "$source")"
75 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
76 |
77 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
78 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
79 | strip_invalid_archs "$binary"
80 | fi
81 |
82 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
83 | # Move the stripped file into its final destination.
84 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
85 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
86 | else
87 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
88 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
89 | fi
90 | fi
91 | }
92 |
93 | # Signs a framework with the provided identity
94 | code_sign_if_enabled() {
95 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
96 | # Use the current code_sign_identitiy
97 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
98 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
99 |
100 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
101 | code_sign_cmd="$code_sign_cmd &"
102 | fi
103 | echo "$code_sign_cmd"
104 | eval "$code_sign_cmd"
105 | fi
106 | }
107 |
108 | # Strip invalid architectures
109 | strip_invalid_archs() {
110 | binary="$1"
111 | # Get architectures for current target binary
112 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
113 | # Intersect them with the architectures we are building for
114 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
115 | # If there are no archs supported by this binary then warn the user
116 | if [[ -z "$intersected_archs" ]]; then
117 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
118 | STRIP_BINARY_RETVAL=0
119 | return
120 | fi
121 | stripped=""
122 | for arch in $binary_archs; do
123 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
124 | # Strip non-valid architectures in-place
125 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
126 | stripped="$stripped $arch"
127 | fi
128 | done
129 | if [[ "$stripped" ]]; then
130 | echo "Stripped $binary of architectures:$stripped"
131 | fi
132 | STRIP_BINARY_RETVAL=1
133 | }
134 |
135 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
136 | wait
137 | fi
138 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
14 |
15 | case "${TARGETED_DEVICE_FAMILY}" in
16 | 1,2)
17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
18 | ;;
19 | 1)
20 | TARGET_DEVICE_ARGS="--target-device iphone"
21 | ;;
22 | 2)
23 | TARGET_DEVICE_ARGS="--target-device ipad"
24 | ;;
25 | 3)
26 | TARGET_DEVICE_ARGS="--target-device tv"
27 | ;;
28 | 4)
29 | TARGET_DEVICE_ARGS="--target-device watch"
30 | ;;
31 | *)
32 | TARGET_DEVICE_ARGS="--target-device mac"
33 | ;;
34 | esac
35 |
36 | install_resource()
37 | {
38 | if [[ "$1" = /* ]] ; then
39 | RESOURCE_PATH="$1"
40 | else
41 | RESOURCE_PATH="${PODS_ROOT}/$1"
42 | fi
43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
44 | cat << EOM
45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
46 | EOM
47 | exit 1
48 | fi
49 | case $RESOURCE_PATH in
50 | *.storyboard)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.xib)
55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
57 | ;;
58 | *.framework)
59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
63 | ;;
64 | *.xcdatamodel)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
67 | ;;
68 | *.xcdatamodeld)
69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
71 | ;;
72 | *.xcmappingmodel)
73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
75 | ;;
76 | *.xcassets)
77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
79 | ;;
80 | *)
81 | echo "$RESOURCE_PATH" || true
82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
83 | ;;
84 | esac
85 | }
86 |
87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
92 | fi
93 | rm -f "$RESOURCES_TO_COPY"
94 |
95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
96 | then
97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
99 | while read line; do
100 | if [[ $line != "${PODS_ROOT}*" ]]; then
101 | XCASSET_FILES+=("$line")
102 | fi
103 | done <<<"$OTHER_XCASSETS"
104 |
105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
106 | fi
107 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_SetupController_TestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_SetupController_TestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SetupController"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SetupController/SetupController.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
8 | PODS_ROOT = ${SRCROOT}/Pods
9 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_SetupController_Tests {
2 | umbrella header "Pods-SetupController_Tests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SetupController_Tests/Pods-SetupController_Tests.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SetupController"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SetupController/SetupController.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
8 | PODS_ROOT = ${SRCROOT}/Pods
9 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SetupController/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.4.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SetupController/SetupController-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_SetupController : NSObject
3 | @end
4 | @implementation PodsDummy_SetupController
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SetupController/SetupController-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SetupController/SetupController-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 | #import "MBBasePageController.h"
14 | #import "MBFinishPageController.h"
15 | #import "MBLabelCell.h"
16 | #import "MBLabelItem.h"
17 | #import "MBProgressPageController.h"
18 | #import "MBSectionFooter.h"
19 | #import "MBSectionHeader.h"
20 | #import "MBSetupController.h"
21 | #import "MBSetupControllerUtilities.h"
22 | #import "MBSetupPageCell.h"
23 | #import "MBSetupPageItem.h"
24 | #import "MBSetupPageSection.h"
25 | #import "MBSwitchCell.h"
26 | #import "MBSwitchItem.h"
27 | #import "MBTableViewPageController.h"
28 | #import "MBTextFieldCell.h"
29 | #import "MBTextFieldItem.h"
30 |
31 | FOUNDATION_EXPORT double SetupControllerVersionNumber;
32 | FOUNDATION_EXPORT const unsigned char SetupControllerVersionString[];
33 |
34 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SetupController/SetupController.modulemap:
--------------------------------------------------------------------------------
1 | framework module SetupController {
2 | umbrella header "SetupController-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SetupController/SetupController.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SetupController
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/Example/SetupController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/SetupController.xcodeproj/xcshareddata/xcschemes/SetupController-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
64 |
70 |
71 |
72 |
73 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/Example/SetupController.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/SetupController.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/SetupController/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Example/SetupController/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/SetupController/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/SetupController/Images.xcassets/SampleImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "SampleImage@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SetupController/Images.xcassets/SampleImage.imageset/SampleImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miximka/SetupController/0f86e3a9579004ba31a01487db93b511952979c2/Example/SetupController/Images.xcassets/SampleImage.imageset/SampleImage@2x.png
--------------------------------------------------------------------------------
/Example/SetupController/MBAccountController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBAccountController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 28/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBTableViewPageController.h"
10 |
11 | @interface MBAccountController : MBTableViewPageController
12 |
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/Example/SetupController/MBAccountController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBAccountController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 28/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBAccountController.h"
10 | #import "MBSetupPageSection.h"
11 | #import "MBSectionHeader.h"
12 | #import "MBSectionFooter.h"
13 | #import "MBTextFieldItem.h"
14 | #import "MBSwitchItem.h"
15 |
16 | @implementation MBAccountController
17 |
18 | #pragma mark - Overridden Methods
19 |
20 | - (void)viewDidLoad
21 | {
22 | [super viewDidLoad];
23 |
24 | __weak MBAccountController *weakSelf = self;
25 |
26 | //Configure section
27 | MBSetupPageSection *section = [MBSetupPageSection sectionWithTitle:@"Configure Account"];
28 | section.headerViewBlock = ^UIView*(MBSetupPageSection *section) {
29 | return [weakSelf preparedPageHeaderViewWithTitle:section.title];
30 | };
31 |
32 | section.headerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
33 | CGSize size = [view sizeThatFits:CGSizeMake(tableView.frame.size.width, 0)];
34 | return size.height;
35 | };
36 |
37 | section.footerViewBlock = ^UIView*(MBSetupPageSection *section) {
38 | MBSectionFooter *footer = [weakSelf preparedFooterViewWithImage:[UIImage imageNamed:@"SampleImage"]
39 | title:@"Sign in with your account credentials"
40 | subtitle:@"Your credentials should have been sent to you by administrator. Contact our support team if you have any questions."];
41 | [footer.topButton setTitle:@"Skip This Step" forState:UIControlStateNormal];
42 | [footer.topButton addTarget:weakSelf action:@selector(skip) forControlEvents:UIControlEventTouchUpInside];
43 | return footer;
44 | };
45 | section.footerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
46 | CGSize size = [view sizeThatFits:CGSizeMake(tableView.frame.size.width, 0)];
47 | return size.height;
48 | };
49 |
50 | MBTextFieldItem *hostItem = [[MBTextFieldItem alloc] initWithTitle:@"Host" text:nil placeholder:@"example.com"];
51 | hostItem.keyboardType = UIKeyboardTypeURL;
52 | hostItem.autocorrectionType = UITextAutocorrectionTypeNo;
53 | hostItem.autocapitalizationType = UITextAutocapitalizationTypeNone;
54 | hostItem.textDidChangeBlock = ^(MBTextFieldItem *item) {
55 | [weakSelf validate];
56 | };
57 | hostItem.validateBlock = ^BOOL(MBSetupPageItem *item) {
58 | return [(MBTextFieldItem *)item text].length > 0;
59 | };
60 |
61 | MBTextFieldItem *loginItem = [[MBTextFieldItem alloc] initWithTitle:@"Login" text:nil placeholder:@"email@example.com"];
62 | loginItem.keyboardType = UIKeyboardTypeEmailAddress;
63 | loginItem.autocorrectionType = UITextAutocorrectionTypeNo;
64 | loginItem.autocapitalizationType = UITextAutocapitalizationTypeNone;
65 | loginItem.textDidChangeBlock = ^(MBTextFieldItem *item) {
66 | [weakSelf validate];
67 | };
68 | loginItem.validateBlock = ^BOOL(MBSetupPageItem *item) {
69 | return [(MBTextFieldItem *)item text].length > 0;
70 | };
71 |
72 | MBTextFieldItem *passwordItem = [[MBTextFieldItem alloc] initWithTitle:@"Password" text:nil placeholder:@"Required"];
73 | passwordItem.autocorrectionType = UITextAutocorrectionTypeNo;
74 | passwordItem.autocapitalizationType = UITextAutocapitalizationTypeNone;
75 | passwordItem.secureTextEntry = YES;
76 | passwordItem.textDidChangeBlock = ^(MBTextFieldItem *item) {
77 | [weakSelf validate];
78 | };
79 | passwordItem.validateBlock = ^BOOL(MBSetupPageItem *item) {
80 | return [(MBTextFieldItem *)item text].length > 0;
81 | };
82 |
83 | MBSwitchItem *connectionTypeItem = [[MBSwitchItem alloc] initWithTitle:@"Use SSL" value:YES];
84 | connectionTypeItem.switchAlignment = MBSwitchAlignmentLeft;
85 |
86 | section.items = @[hostItem, loginItem, passwordItem, connectionTypeItem];
87 | self.sections = @[section];
88 | }
89 |
90 | - (BOOL)validate
91 | {
92 | BOOL success = [super validate];
93 | self.nextButtonItem.enabled = success;
94 |
95 | return success;
96 | }
97 |
98 | @end
99 |
--------------------------------------------------------------------------------
/Example/SetupController/MBAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBAppDelegate.h
3 | // SetupController
4 | //
5 | // Created by CocoaPods on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MBAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Example/SetupController/MBAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBAppDelegate.m
3 | // SetupController
4 | //
5 | // Created by CocoaPods on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBAppDelegate.h"
10 |
11 | @implementation MBAppDelegate
12 |
13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
14 | {
15 | // Override point for customization after application launch.
16 | return YES;
17 | }
18 |
19 | - (void)applicationWillResignActive:(UIApplication *)application
20 | {
21 | // 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.
22 | // 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.
23 | }
24 |
25 | - (void)applicationDidEnterBackground:(UIApplication *)application
26 | {
27 | // 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.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | - (void)applicationWillEnterForeground:(UIApplication *)application
32 | {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | - (void)applicationDidBecomeActive:(UIApplication *)application
37 | {
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 | {
43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
44 | }
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/Example/SetupController/MBBackupController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBBackupController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 31/01/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBTableViewPageController.h"
10 |
11 | @interface MBBackupController : MBTableViewPageController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/SetupController/MBBackupController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBBackupController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 31/01/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBBackupController.h"
10 | #import "MBSetupPageSection.h"
11 | #import "MBSectionHeader.h"
12 | #import "MBSectionFooter.h"
13 | #import "MBLabelItem.h"
14 |
15 | @interface MBLabel : UILabel
16 | @property (nonatomic) UIEdgeInsets insets;
17 | @end
18 |
19 | @implementation MBLabel
20 | - (void)drawTextInRect:(CGRect)rect {
21 | [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.insets)];
22 | }
23 | @end
24 |
25 | @interface MBBackupController ()
26 | @end
27 |
28 | @implementation MBBackupController
29 |
30 | - (void)backupWithItem:(MBSetupPageItem *)item
31 | {
32 | [self proceedToNextPage];
33 | }
34 |
35 | #pragma mark - Overridden Methods
36 |
37 | - (void)viewDidLoad
38 | {
39 | [super viewDidLoad];
40 |
41 | self.hidesNextButton = YES;
42 |
43 | __weak MBBackupController *weakSelf = self;
44 |
45 | //Header section
46 | MBSetupPageSection *headerSection = [MBSetupPageSection sectionWithTitle:@"Restore Backup"];
47 | headerSection.headerViewBlock = ^UIView*(MBSetupPageSection *section) {
48 | return [weakSelf preparedPageHeaderViewWithTitle:section.title];
49 | };
50 |
51 | headerSection.headerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
52 | CGSize size = [view sizeThatFits:CGSizeMake(tableView.frame.size.width, 0)];
53 | return size.height;
54 | };
55 |
56 | //Backup section
57 | MBSetupPageSection *backupSection = [MBSetupPageSection sectionWithTitle:@"All Backups"];
58 |
59 | backupSection.headerViewBlock = ^UIView *(MBSetupPageSection *section) {
60 | MBLabel *label = [[MBLabel alloc] init];
61 | label.insets = UIEdgeInsetsMake(0, 10.0, 0, 0);
62 | label.text = [section.title uppercaseString];
63 | return label;
64 | };
65 |
66 | backupSection.headerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
67 | return 44.0;
68 | };
69 |
70 | backupSection.footerViewBlock = ^UIView *(MBSetupPageSection *section) {
71 | MBSectionFooter *footer = [weakSelf preparedFooterViewWithImage:nil
72 | title:nil
73 | subtitle:nil];
74 | [footer.topButton setTitle:@"Skip This Step" forState:UIControlStateNormal];
75 | [footer.topButton addTarget:weakSelf action:@selector(skip) forControlEvents:UIControlEventTouchUpInside];
76 | return footer;
77 | };
78 |
79 | backupSection.footerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
80 | CGSize size = [view sizeThatFits:CGSizeMake(tableView.frame.size.width, 0)];
81 | return size.height;
82 | };
83 |
84 | MBLabelItem *backupItem1 = [[MBLabelItem alloc] initWithTitle:@"12 June 2015 23:39 pm" detail:@"My iPad 2" style:UITableViewCellStyleSubtitle];
85 | backupItem1.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
86 | backupItem1.didSelectBlock = ^(MBSetupPageItem *item) {
87 | [weakSelf backupWithItem:item];
88 | [item deselectAnimated:YES];
89 | };
90 |
91 | MBLabelItem *backupItem2 = [[MBLabelItem alloc] initWithTitle:@"22 June 2015 16:01 pm" detail:@"My iPad 2" style:UITableViewCellStyleSubtitle];
92 | backupItem2.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
93 | backupItem2.didSelectBlock = ^(MBSetupPageItem *item) {
94 | [weakSelf backupWithItem:item];
95 | [item deselectAnimated:YES];
96 | };
97 |
98 | backupSection.items = @[backupItem1, backupItem2];
99 | self.sections = @[headerSection, backupSection];
100 |
101 | // Use table view separators
102 | [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
103 | }
104 |
105 | - (UITableViewCellSeparatorStyle)customCellSeparatorStyle {
106 | // Disable custom separators
107 | return UITableViewCellSeparatorStyleNone;
108 | }
109 |
110 | - (BOOL)validate
111 | {
112 | BOOL success = [super validate];
113 | self.nextButtonItem.enabled = success;
114 |
115 | return success;
116 | }
117 |
118 | @end
119 |
--------------------------------------------------------------------------------
/Example/SetupController/MBProgressController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSettingUpAccountController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBProgressPageController.h"
10 |
11 | @interface MBProgressController : MBProgressPageController
12 |
13 | @property (nonatomic) NSString *labelTitle;
14 | @property (nonatomic) NSInteger tag;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Example/SetupController/MBProgressController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSettingUpAccountController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBProgressController.h"
10 |
11 | @interface MBProgressController ()
12 | @property (nonatomic) NSTimer *timer;
13 | @end
14 |
15 | @implementation MBProgressController
16 |
17 | - (void)startActivity
18 | {
19 | [self.activityIndicator startAnimating];
20 | _timer = [NSTimer scheduledTimerWithTimeInterval:2.0
21 | target:self
22 | selector:@selector(timerFired:)
23 | userInfo:nil
24 | repeats:NO];
25 | }
26 |
27 | - (void)cancelActivity
28 | {
29 | [self.activityIndicator stopAnimating];
30 |
31 | [_timer invalidate];
32 | _timer = nil;
33 | }
34 |
35 | - (void)finishActivity
36 | {
37 | [self.activityIndicator stopAnimating];
38 | [self proceedToNextPage];
39 | }
40 |
41 | #pragma mark - Overridden Methods
42 |
43 | - (void)viewWillAppear:(BOOL)animated
44 | {
45 | [super viewWillAppear:animated];
46 | self.titleLabel.text = self.labelTitle;
47 | [self startActivity];
48 | }
49 |
50 | - (void)viewWillDisappear:(BOOL)animated
51 | {
52 | [super viewWillDisappear:animated];
53 | [self cancelActivity];
54 | }
55 |
56 | #pragma mark - NSTimer callback
57 |
58 | - (void)timerFired:(NSTimer *)timer
59 | {
60 | _timer = nil;
61 | [self finishActivity];
62 | }
63 |
64 | @end
65 |
--------------------------------------------------------------------------------
/Example/SetupController/MBSampleSetupController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSampleSetupController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupController.h"
10 |
11 | @interface MBSampleSetupController : MBSetupController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/SetupController/MBSampleSetupController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSampleSetupController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSampleSetupController.h"
10 | #import "MBAccountController.h"
11 | #import "MBProgressController.h"
12 | #import "MBWelcomeController.h"
13 | #import "MBBackupController.h"
14 | #import "MBProgressController.h"
15 |
16 | @interface MBSampleSetupController()
17 | @property (nonatomic) MBAccountController *accountController;
18 | @end
19 |
20 | @implementation MBSampleSetupController
21 |
22 | - (MBWelcomeController *)finishController
23 | {
24 | MBWelcomeController *controller = [[MBWelcomeController alloc] init];
25 | return controller;
26 | }
27 |
28 | - (IBAction)getStarted:(id)sender
29 | {
30 | [self pushNext];
31 | }
32 |
33 | #pragma mark - Overridden Methods
34 |
35 | - (void)viewDidLoad
36 | {
37 | [super viewDidLoad];
38 |
39 | MBAccountController *initialController = [[MBAccountController alloc] init];
40 | [self setViewControllers:@[initialController] animated:NO];
41 | }
42 |
43 | #pragma mark - MBSetupControllerDataSource
44 |
45 | - (UIViewController *)setupController:(MBSetupController *)setupController viewControllerAfterViewController:(UIViewController *)viewController
46 | {
47 | if ([viewController isKindOfClass:[MBAccountController class]]) {
48 | MBProgressController *progressController = [[MBProgressController alloc] init];
49 | progressController.labelTitle = @"It may take a while to set up your account...";
50 | progressController.tag = 0;
51 | return progressController;
52 | }
53 |
54 | if ([viewController isKindOfClass:[MBBackupController class]]) {
55 | if (viewController.isSkipped) {
56 | return [self finishController];
57 | } else {
58 | MBProgressController *progressController = [[MBProgressController alloc] init];
59 | progressController.labelTitle = @"It may take a while to restore a backup...";
60 | progressController.tag = 1;
61 | return progressController;
62 | }
63 | }
64 |
65 | if ([viewController isKindOfClass:[MBProgressController class]]) {
66 | MBProgressController *progressController = (MBProgressController *)viewController;
67 | if (progressController.tag == 0) {
68 | return [[MBBackupController alloc] init];
69 | }
70 |
71 | return [self finishController];
72 | }
73 |
74 | return nil;
75 | }
76 |
77 | @end
78 |
--------------------------------------------------------------------------------
/Example/SetupController/MBViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBViewController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MBViewController : UIViewController
12 |
13 | - (IBAction)startSetup:(id)sender;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Example/SetupController/MBViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBViewController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBViewController.h"
10 | #import "MBSampleSetupController.h"
11 |
12 | @interface MBViewController ()
13 |
14 | @end
15 |
16 | @implementation MBViewController
17 |
18 | - (IBAction)startSetup:(id)sender
19 | {
20 | MBSampleSetupController *setupController = [[MBSampleSetupController alloc] init];
21 | setupController.dataSource = setupController;
22 | setupController.delegate = self;
23 |
24 | [self presentViewController:setupController animated:YES completion:nil];
25 | }
26 |
27 | #pragma mark - MBSetupControllerDelegate
28 |
29 | - (void)setupControllerDidFinish:(MBSetupController *)setupController
30 | {
31 | [self dismissViewControllerAnimated:YES completion:nil];
32 | }
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/Example/SetupController/MBWelcomeController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBWelcomeController.h
3 | // SetupController
4 | //
5 | // Created by mab on 23/02/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBFinishPageController.h"
10 |
11 | @interface MBWelcomeController : MBFinishPageController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/SetupController/MBWelcomeController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBWelcomeController.m
3 | // SetupController
4 | //
5 | // Created by mab on 23/02/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBWelcomeController.h"
10 |
11 | @interface MBWelcomeController ()
12 |
13 | @end
14 |
15 | @implementation MBWelcomeController
16 |
17 | - (IBAction)getStarted:(id)sender
18 | {
19 | [self proceedToNextPage];
20 | }
21 |
22 | #pragma - Overridden Methods
23 |
24 | - (void)viewDidLoad
25 | {
26 | [super viewDidLoad];
27 |
28 | self.imageView.image = [UIImage imageNamed:@"SampleImage"];
29 | self.titleLabel.text = [NSString stringWithFormat:@"Welcome to SetupController"];
30 |
31 | [self.button setTitle:@"Get Started" forState:UIControlStateNormal];
32 | [self.button addTarget:self action:@selector(getStarted:) forControlEvents:UIControlEventTouchUpInside];
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/Example/SetupController/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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example/SetupController/SetupController-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Example/SetupController/SetupController-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | #warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | @import UIKit;
15 | @import Foundation;
16 | #endif
17 |
--------------------------------------------------------------------------------
/Example/SetupController/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/SetupController/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 10/23/2017.
6 | // Copyright (c) 2017 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "MBAppDelegate.h"
11 |
12 | int main(int argc, char * argv[])
13 | {
14 | @autoreleasepool {
15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBAppDelegate class]));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Example/Tests/Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/Tests/Tests-Prefix.pch:
--------------------------------------------------------------------------------
1 | // The contents of this file are implicitly included at the beginning of every test case source file.
2 |
3 | #ifdef __OBJC__
4 |
5 |
6 |
7 | #endif
8 |
--------------------------------------------------------------------------------
/Example/Tests/Tests.m:
--------------------------------------------------------------------------------
1 | //
2 | // SetupControllerTests.m
3 | // SetupControllerTests
4 | //
5 | // Created by Maksim Bauer on 10/23/2017.
6 | // Copyright (c) 2017 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | @import XCTest;
10 |
11 | @interface Tests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation Tests
16 |
17 | - (void)setUp
18 | {
19 | [super setUp];
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | - (void)tearDown
24 | {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | [super tearDown];
27 | }
28 |
29 | - (void)testExample
30 | {
31 | }
32 |
33 | @end
34 |
35 |
--------------------------------------------------------------------------------
/Example/Tests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Images/iPadLandscape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miximka/SetupController/0f86e3a9579004ba31a01487db93b511952979c2/Images/iPadLandscape.png
--------------------------------------------------------------------------------
/Images/iPhonePortrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miximka/SetupController/0f86e3a9579004ba31a01487db93b511952979c2/Images/iPhonePortrait.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Maksim Bauer
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SetupController
2 |
3 | [](https://travis-ci.org/miximka/SetupController)
4 | [](http://cocoadocs.org/docsets/SetupController)
5 | [](http://cocoadocs.org/docsets/SetupController)
6 | [](http://cocoadocs.org/docsets/SetupController)
7 |
8 | A `MBSetupController` is a subclass of `UIViewController` controller that acts like a **wizard** or **setup assistant** to present a sequence of dialog views that lead the user through a series of steps.
9 |
10 | It has been inspired by the iOS Setup.app application which is presented to user during the first-time device setup.
11 |
12 | 
13 | 
14 |
15 | ## Features
16 |
17 | - Support for universal iOS8 applications with portrait and landscape orientations.
18 | - A couple of predefined page view controllers like `MBTableViewPageController`, `MBProgressPageController` or `MBFinishPageController` offers a lot of functionality out-of-the-box to quickly build pages for `MBSetupController`.
19 | - Predefined `MBTableViewPageController` use blocks instead of delegate calls to keep the table view's section code in one place, supports autolayout, and autosizing cells (iOS8 feature).
20 |
21 | ## Demo
22 |
23 | A demo project is included in the repository.
24 | To run it, clone the repo, and run `pod install` from the Example directory first.
25 |
26 | ## Requirements
27 |
28 | | Minimum iOS Target | Notes |
29 | |:-:|:-:|
30 | | iOS 8 | - |
31 |
32 | ## Installation
33 |
34 | SetupController is available through [CocoaPods](http://cocoapods.org). To install
35 | it, simply add the following line to your Podfile:
36 |
37 | pod "SetupController"
38 |
39 | ##Architecture
40 |
41 | ###Setup View Controller
42 | - `MBSetupController`
43 |
44 | ###Page View Controllers
45 |
46 | - `MBBasePageController`
47 | - `MBTableViewPageController`
48 | - `MBProgressPageController`
49 | - `MBFinishPageController`
50 |
51 | ###Items and cells for `MBTableViewPageController`
52 | - `MBSetupPageItem`
53 | - `MBLabelItem`
54 | - `MBTextFieldItem`
55 | - `MBSetupPageCell`
56 | - `MBLabelCell`
57 | - `MBTextFieldCell`
58 |
59 | ##Quick Start
60 |
61 | ###Implement setup controller's data source
62 |
63 | 1. Subclass `MBSetupController`
64 |
65 | ```objc
66 | @interface MBSampleSetupController : MBSetupController
67 | ```
68 |
69 | 2. Set initial view controller
70 |
71 | ```objc
72 | - (void)viewDidLoad {
73 | [super viewDidLoad];
74 | MBAccountController *initialController = [[MBAccountController alloc] init];
75 | [self setViewControllers:@[initialController] animated:NO];
76 | }
77 | ```
78 |
79 |
80 | 3. Implement `MBSetupControllerDataSource` protocol
81 |
82 | ```objc
83 | - (UIViewController *)setupController:(MBSetupController *)setupController viewControllerAfterViewController:(UIViewController *)viewController {
84 | if ([viewController isKindOfClass:[MBAccountController class]]) {
85 | MBProgressController *progressController = [[MBProgressController alloc] init];
86 | progressController.labelTitle = @"It may take a while to set up your account...";
87 | return progressController;
88 | }
89 |
90 | return nil;
91 | }
92 | ```
93 |
94 | ###Implement Page View Controller
95 |
96 | 1. Subclass `MBTableViewPageController `
97 |
98 | ```objc
99 | @interface MBAccountController : MBTableViewPageController
100 | ```
101 | Next steps are all inside `-viewDidLoad`.
102 |
103 |
104 | 2. Create section
105 |
106 | ```objc
107 | MBSetupPageSection *section = [MBSetupPageSection sectionWithTitle:@"Configure Account"];
108 | ```
109 |
110 | 3. Configure section's header
111 |
112 | ```objc
113 | section.headerViewBlock = ^UIView*(MBSetupPageSection *section) {
114 | return [weakSelf preparedPageHeaderViewWithTitle:section.title];
115 | };
116 |
117 | section.headerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
118 | CGSize size = [view sizeThatFits:CGSizeMake(tableView.frame.size.width, 0)];
119 | return size.height;
120 | };
121 | ```
122 |
123 | 4. Configure section's footer
124 |
125 | ```objc
126 | section.footerViewBlock = ^UIView*(MBSetupPageSection *section) {
127 | MBSectionFooter *footer = [weakSelf preparedFooterViewWithImage:[UIImage imageNamed:@"SampleImage"]
128 | title:@"Sign in with your account credentials"
129 | subtitle:@"Your credentials should have been sent to you by administrator. Contact our support team if you have any questions."];
130 | [footer.topButton setTitle:@"Skip This Step" forState:UIControlStateNormal];
131 | [footer.topButton addTarget:weakSelf action:@selector(skip) forControlEvents:UIControlEventTouchUpInside];
132 | return footer;
133 | };
134 |
135 | section.footerHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageSection *section, UIView *view) {
136 | CGSize size = [view sizeThatFits:CGSizeMake(tableView.frame.size.width, 0)];
137 | return size.height;
138 | };
139 | ```
140 |
141 | 5. Create and configure text field item
142 |
143 | ```objc
144 | MBTextFieldItem *hostItem = [[MBTextFieldItem alloc] initWithTitle:@"Host" text:nil placeholder:@"example.com"];
145 | hostItem.keyboardType = UIKeyboardTypeURL;
146 | hostItem.autocorrectionType = UITextAutocorrectionTypeNo;
147 | hostItem.autocapitalizationType = UITextAutocapitalizationTypeNone;
148 | hostItem.textDidChangeBlock = ^(MBTextFieldItem *item) {
149 | [weakSelf validate];
150 | };
151 | hostItem.validateBlock = ^BOOL(MBSetupPageItem *item) {
152 | return [(MBTextFieldItem *)item text].length > 0;
153 | };
154 | ```
155 |
156 | 6. Add item to section
157 |
158 | ```objc
159 | section.items = @[hostItem];
160 | ```
161 |
162 | 7. Set `sections` property of the `MBTableViewPageController`
163 |
164 | ```objc
165 | self.sections = @[section];
166 | ```
167 |
168 | ###Present Setup Controller
169 |
170 | ```objc
171 | MBSampleSetupController *setupController = [[MBSampleSetupController alloc] init];
172 | setupController.dataSource = setupController;
173 |
174 | [self presentViewController:setupController animated:YES completion:nil];
175 | ```
176 |
177 | ## Author
178 |
179 | Maksim Bauer, miximka@gmail.com
180 |
181 | ## License
182 |
183 | SetupController is available under the MIT license. See the LICENSE file for more info.
184 |
--------------------------------------------------------------------------------
/SetupController.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint SetupController.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'SetupController'
11 | s.version = '0.5.0'
12 | s.summary = 'MBSetupController can present a sequence of dialog views that lead the user through a series of steps like a wizard or a setup assistant.'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = <<-DESC
21 | A MBSetupController is a subclass of UIViewController controller that acts like a wizard or setup assistant to present a sequence of dialog views that lead the user through a series of steps.
22 | DESC
23 |
24 | s.homepage = 'https://github.com/miximka/SetupController'
25 | # s.screenshots = 'Images/iPhonePortrait.png', 'Images/iPadLandscape.png'
26 | s.license = { :type => 'MIT', :file => 'LICENSE' }
27 | s.author = { 'Maksim Bauer' => 'miximka@gmail.com' }
28 | s.source = { :git => 'https://github.com/miximka/SetupController.git', :tag => s.version.to_s }
29 | s.social_media_url = 'https://twitter.com/miximka'
30 |
31 | s.ios.deployment_target = '8.0'
32 |
33 | s.source_files = 'SetupController/Classes/**/*'
34 |
35 | # s.resource_bundles = {
36 | # 'SetupController' => ['SetupController/Assets/*.png']
37 | # }
38 |
39 | # s.public_header_files = 'Pod/Classes/**/*.h'
40 | # s.frameworks = 'UIKit', 'MapKit'
41 | # s.dependency 'AFNetworking', '~> 2.3'
42 | end
43 |
--------------------------------------------------------------------------------
/SetupController/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miximka/SetupController/0f86e3a9579004ba31a01487db93b511952979c2/SetupController/Assets/.gitkeep
--------------------------------------------------------------------------------
/SetupController/Classes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miximka/SetupController/0f86e3a9579004ba31a01487db93b511952979c2/SetupController/Classes/.gitkeep
--------------------------------------------------------------------------------
/SetupController/Classes/MBBasePageController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBBasePageController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupController.h"
10 |
11 | @interface MBBasePageController : UIViewController
12 |
13 | /**
14 | Content view of the receiver. Content view is the default superview for content dispayed by the receiver's view.
15 | */
16 | @property (nonatomic, readonly, nullable) UIView *contentView;
17 |
18 | #pragma mark - Customizing Appearance
19 |
20 | /**
21 | Horizontal margin to be set in a regular size class environment.
22 | */
23 | @property (nonatomic) CGFloat horizontalMargin;
24 |
25 | /**
26 | Set to YES to let controller automatically increase horizontal margin between superview and controller's view when in regular horizontal size class.
27 | */
28 | @property (nonatomic) BOOL automaticallyAdjustMargins;
29 |
30 | /**
31 | A Boolean value that determines whether the back button is hidden.
32 | */
33 | @property (nonatomic) BOOL hidesBackButton;
34 |
35 | /**
36 | Bar button item presented on the right of the navigation bar when receiver is on the top of the setup controller stack.
37 | */
38 | @property (nonatomic, nullable) UIBarButtonItem *nextButtonItem;
39 |
40 | /**
41 | A Boolean value that determines whether the next button is hidden.
42 | */
43 | @property (nonatomic) BOOL hidesNextButton;
44 |
45 | #pragma mark - Navigation
46 |
47 | /**
48 | Called when next bar button item is tapped.
49 | Default implementation simply calls -proceedToNextPage
50 | */
51 | - (void)handleNextButtonTap;
52 |
53 | /**
54 | Call this to proceed to the previous page controller.
55 | */
56 | - (void)proceedToPreviousPage;
57 |
58 | /**
59 | Call this to proceed to the next page controller.
60 | */
61 | - (void)proceedToNextPage;
62 |
63 | /**
64 | Indicates whether setup controller should remove the receiver from stack when its done (i.e. another controller has been pushed on stack above it due proceedToNextPage call)
65 | Default is NO.
66 | */
67 | @property (nonatomic) BOOL removeWhenDone;
68 |
69 | /**
70 | Will be set to YES if receiver has been skipped.
71 | */
72 | @property (nonatomic, readonly, getter=isSkipped) BOOL skipped;
73 |
74 | /**
75 | Call this to skip the optional receiver.
76 | */
77 | - (void)skip;
78 |
79 | @end
80 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBBasePageController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBBasePageController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBBasePageController.h"
10 | #import "MBSetupControllerUtilities.h"
11 |
12 | #define PAGE_DEFAULT_HORIZONTAL_MARGIN 100.0
13 |
14 | @interface MBSetupController (Friend)
15 | - (void)pageControllerProceedToPreviousPage:(UIViewController *)controller;
16 | - (void)pageControllerProceedToNextPage:(UIViewController *)controller;
17 | @end
18 |
19 | @interface MBBasePageController ()
20 | @property (nonatomic) NSLayoutConstraint *leftMarginConstraint;
21 | @property (nonatomic) NSLayoutConstraint *rightMarginConstraint;
22 | @end
23 |
24 | @implementation MBBasePageController
25 |
26 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
27 | {
28 | self = [super initWithCoder:aDecoder];
29 | if (self) {
30 | _horizontalMargin = PAGE_DEFAULT_HORIZONTAL_MARGIN;
31 | _automaticallyAdjustMargins = YES;
32 | }
33 | return self;
34 | }
35 |
36 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
37 | {
38 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
39 | if (self) {
40 | _horizontalMargin = PAGE_DEFAULT_HORIZONTAL_MARGIN;
41 | _automaticallyAdjustMargins = YES;
42 | }
43 | return self;
44 | }
45 |
46 | - (void)setHidesBackButton:(BOOL)hide
47 | {
48 | _hidesBackButton = hide;
49 | self.navigationItem.hidesBackButton = hide;
50 | }
51 |
52 | - (void)setHidesNextButton:(BOOL)hide
53 | {
54 | _hidesNextButton = hide;
55 | [self configureBarButtonItemsAnimated:NO];
56 | }
57 |
58 | - (void)configureBarButtonItemsAnimated:(BOOL)animate
59 | {
60 | if (!_nextButtonItem) {
61 | _nextButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Next" style:UIBarButtonItemStylePlain target:self action:@selector(nextTap:)];
62 | }
63 |
64 | UINavigationItem *navItem = self.navigationItem;
65 | navItem.hidesBackButton = self.hidesBackButton;
66 |
67 | BOOL shouldPresentNextButton = !self.hidesNextButton;
68 | BOOL presentingNextButton = [navItem.rightBarButtonItems containsObject:_nextButtonItem];
69 |
70 | NSMutableArray *mutableRightBarButtonItems = [navItem.rightBarButtonItems mutableCopy];
71 |
72 | if (shouldPresentNextButton && !presentingNextButton) {
73 | //Add next button
74 | if (!mutableRightBarButtonItems) {
75 | mutableRightBarButtonItems = [NSMutableArray new];
76 | }
77 |
78 | [mutableRightBarButtonItems addObject:_nextButtonItem];
79 | [navItem setRightBarButtonItems:mutableRightBarButtonItems animated:animate];
80 | } else {
81 | //Remove next button
82 | [mutableRightBarButtonItems removeObject:_nextButtonItem];
83 | [navItem setRightBarButtonItems:mutableRightBarButtonItems animated:animate];
84 | }
85 | }
86 |
87 | - (IBAction)nextTap:(id)sender
88 | {
89 | BOOL success = YES;
90 |
91 | UIView *firstResponder = [self.view mbFindFirstResponder];
92 | if (firstResponder) {
93 | success = [firstResponder resignFirstResponder];
94 | }
95 |
96 | [self handleNextButtonTap];
97 | }
98 |
99 | - (void)adjustMargins
100 | {
101 | if (!self.automaticallyAdjustMargins) {
102 | return;
103 | }
104 |
105 | CGFloat margin = 0.0;
106 | if (self.mbIsRegularHorizontalSizeClass) {
107 | margin = self.horizontalMargin;
108 | }
109 |
110 | _leftMarginConstraint.constant = margin;
111 | _rightMarginConstraint.constant = margin;
112 | }
113 |
114 | - (void)addContentView
115 | {
116 | UIView *contentView = [[UIView alloc] init];
117 | contentView.translatesAutoresizingMaskIntoConstraints = NO;
118 | [self.view addSubview:contentView];
119 |
120 | _leftMarginConstraint = [NSLayoutConstraint constraintWithItem:contentView
121 | attribute:NSLayoutAttributeLeading
122 | relatedBy:NSLayoutRelationEqual
123 | toItem:self.view
124 | attribute:NSLayoutAttributeLeading
125 | multiplier:1.0
126 | constant:0];
127 | [self.view addConstraint:_leftMarginConstraint];
128 |
129 | _rightMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
130 | attribute:NSLayoutAttributeTrailing
131 | relatedBy:NSLayoutRelationEqual
132 | toItem:contentView
133 | attribute:NSLayoutAttributeTrailing
134 | multiplier:1.0
135 | constant:0];
136 | [self.view addConstraint:_rightMarginConstraint];
137 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
138 | _contentView = contentView;
139 | }
140 |
141 | #pragma mark - Overridden Methods
142 |
143 | - (void)viewDidLoad
144 | {
145 | [super viewDidLoad];
146 |
147 | self.view.backgroundColor = [UIColor whiteColor];
148 | [self addContentView];
149 | [self configureBarButtonItemsAnimated:NO];
150 | [self adjustMargins];
151 | }
152 |
153 | - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
154 | {
155 | [super traitCollectionDidChange:previousTraitCollection];
156 | [self adjustMargins];
157 | }
158 |
159 | #pragma mark - Navigation
160 |
161 | - (void)handleNextButtonTap
162 | {
163 | [self proceedToNextPage];
164 | }
165 |
166 | - (void)proceedToPreviousPage
167 | {
168 | [self.mbSetupController pageControllerProceedToPreviousPage:self];
169 | }
170 |
171 | - (void)proceedToNextPage
172 | {
173 | [self.mbSetupController pageControllerProceedToNextPage:self];
174 | }
175 |
176 | - (void)skip
177 | {
178 | _skipped = YES;
179 | [self.mbSetupController pageControllerProceedToNextPage:self];
180 | }
181 |
182 | @end
183 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBFinishPageController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBFinishPageController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBBasePageController.h"
10 |
11 | @interface MBFinishPageController : MBBasePageController
12 |
13 | @property (nonatomic, readonly, nullable) UIImageView *imageView;
14 | @property (nonatomic, readonly, nullable) UILabel *titleLabel;
15 | @property (nonatomic, readonly, nullable) UIButton *button;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBFinishPageController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBFinishPageController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBFinishPageController.h"
10 | #import "MBSetupControllerUtilities.h"
11 |
12 | @interface MBFinishPageController ()
13 | @end
14 |
15 | @implementation MBFinishPageController
16 |
17 | - (instancetype)initWithCoder:(NSCoder *)coder
18 | {
19 | self = [super initWithCoder:coder];
20 | if (self) {
21 | self.hidesNextButton = YES;
22 | self.hidesBackButton = YES;
23 | }
24 | return self;
25 | }
26 |
27 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
28 | {
29 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
30 | if (self) {
31 | self.hidesNextButton = YES;
32 | self.hidesBackButton = YES;
33 | }
34 | return self;
35 | }
36 |
37 | - (void)addContentViews
38 | {
39 | UIView *contentView = self.contentView;
40 |
41 | UIImageView *imageView = [[UIImageView alloc] init];
42 | imageView.translatesAutoresizingMaskIntoConstraints = NO;
43 | [contentView addSubview:imageView];
44 | _imageView = imageView;
45 |
46 | UILabel *titleLabel = [MBSetupControllerUtilities captionStyledLabel];
47 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
48 | [contentView addSubview:titleLabel];
49 | _titleLabel = titleLabel;
50 |
51 | UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
52 | button.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue-Thin" size:21];
53 | button.translatesAutoresizingMaskIntoConstraints = NO;
54 | [contentView addSubview:button];
55 | _button = button;
56 |
57 | NSDictionary *views = NSDictionaryOfVariableBindings(imageView, titleLabel, button);
58 |
59 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:imageView
60 | attribute:NSLayoutAttributeCenterX
61 | relatedBy:NSLayoutRelationEqual
62 | toItem:contentView
63 | attribute:NSLayoutAttributeCenterX
64 | multiplier:1.0
65 | constant:0]];
66 |
67 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[titleLabel]|" options:0 metrics:nil views:views]];
68 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:titleLabel
69 | attribute:NSLayoutAttributeCenterY
70 | relatedBy:NSLayoutRelationEqual
71 | toItem:contentView
72 | attribute:NSLayoutAttributeCenterY
73 | multiplier:1.0
74 | constant:-20.0]];
75 |
76 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:button
77 | attribute:NSLayoutAttributeCenterX
78 | relatedBy:NSLayoutRelationEqual
79 | toItem:contentView
80 | attribute:NSLayoutAttributeCenterX
81 | multiplier:1.0
82 | constant:0]];
83 |
84 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[imageView]-[titleLabel]-[button]" options:0 metrics:nil views:views]];
85 | }
86 |
87 | #pragma mark - Overridden Methods
88 |
89 | - (void)viewDidLoad
90 | {
91 | [super viewDidLoad];
92 | [self addContentViews];
93 | }
94 |
95 | @end
96 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBLabelCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBLabelCell.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 31/01/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageCell.h"
10 |
11 | @interface MBLabelCell : MBSetupPageCell
12 | @end
13 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBLabelCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBLabelCell.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 31/01/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBLabelCell.h"
10 | #import "MBLabelItem.h"
11 |
12 | @implementation MBLabelCell
13 |
14 | #pragma mark - Overridden Methods
15 |
16 | - (void)cellWillAppear
17 | {
18 | [super cellWillAppear];
19 |
20 | MBLabelItem *item = (MBLabelItem *)self.item;
21 | self.textLabel.text = item.title;
22 | self.detailTextLabel.text = item.detail;
23 | }
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBLabelItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBLabelItem.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 31/01/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageItem.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MBLabelItem : MBSetupPageItem
14 |
15 | - (instancetype)initWithTitle:(nullable NSString *)title detail:(nullable NSString *)detail style:(UITableViewCellStyle)style;
16 |
17 | @property (nonatomic, nullable) NSString *detail;
18 | @property (nonatomic) UITableViewCellStyle style;
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
--------------------------------------------------------------------------------
/SetupController/Classes/MBLabelItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBLabelItem.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 31/01/15.
6 | // Copyright (c) 2015 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBLabelItem.h"
10 | #import "MBLabelCell.h"
11 |
12 | @implementation MBLabelItem
13 |
14 | - (instancetype)initWithTitle:(NSString *)title detail:(NSString *)detail style:(UITableViewCellStyle)style
15 | {
16 | self = [super initWithTitle:title];
17 | if (self) {
18 | _detail = detail;
19 | _style = style;
20 |
21 | self.createCellBlock = ^MBSetupPageCell *(MBSetupPageItem *item) {
22 | MBLabelItem *labelItem = (MBLabelItem *)item;
23 | MBLabelCell *cell = [[MBLabelCell alloc] initWithStyle:labelItem.style reuseIdentifier:labelItem.cellIdentifier];
24 | return cell;
25 | };
26 |
27 | self.configureCellBlock = ^(MBSetupPageItem *item, MBSetupPageCell *cell) {
28 | cell.item = item;
29 | };
30 |
31 | self.cellHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageItem *item, MBSetupPageCell *cell) {
32 | cell.item = item;
33 | CGSize fittingSize = CGSizeMake(tableView.bounds.size.width, 0);
34 | CGSize size = [cell sizeThatFits:fittingSize];
35 | return size.height;
36 | };
37 |
38 | }
39 | return self;
40 | }
41 |
42 | - (instancetype)initWithTitle:(NSString *)title {
43 | return [self initWithTitle:title detail:nil style:UITableViewCellStyleDefault];
44 | }
45 |
46 | - (NSString *)cellIdentifier
47 | {
48 | return @"MBLabelItem";
49 | }
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBProgressPageController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBProgressPageController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBBasePageController.h"
10 |
11 | @interface MBProgressPageController : MBBasePageController
12 |
13 | @property (nonatomic, readonly, nullable) UIActivityIndicatorView *activityIndicator;
14 | @property (nonatomic, readonly, nullable) UILabel *titleLabel;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBProgressPageController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBProgressPageController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 29/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBProgressPageController.h"
10 |
11 | @interface MBProgressPageController ()
12 | @end
13 |
14 | @implementation MBProgressPageController
15 |
16 | - (instancetype)initWithCoder:(NSCoder *)coder
17 | {
18 | self = [super initWithCoder:coder];
19 | if (self) {
20 | self.hidesNextButton = YES;
21 | self.removeWhenDone = YES;
22 | }
23 | return self;
24 | }
25 |
26 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
27 | {
28 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
29 | if (self) {
30 | self.hidesNextButton = YES;
31 | self.removeWhenDone = YES;
32 | }
33 | return self;
34 | }
35 |
36 | - (void)addContentViews
37 | {
38 | UIView *contentView = self.contentView;
39 |
40 | UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
41 | indicator.translatesAutoresizingMaskIntoConstraints = NO;
42 | [contentView addSubview:indicator];
43 | _activityIndicator = indicator;
44 |
45 | UILabel *titleLabel = [[UILabel alloc] init];
46 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
47 | titleLabel.textAlignment = NSTextAlignmentCenter;
48 | titleLabel.numberOfLines = 0;
49 | [contentView addSubview:titleLabel];
50 | _titleLabel = titleLabel;
51 |
52 | NSDictionary *views = NSDictionaryOfVariableBindings(indicator, titleLabel);
53 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:indicator
54 | attribute:NSLayoutAttributeCenterX
55 | relatedBy:NSLayoutRelationEqual
56 | toItem:contentView
57 | attribute:NSLayoutAttributeCenterX
58 | multiplier:1.0
59 | constant:0]];
60 |
61 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:titleLabel
62 | attribute:NSLayoutAttributeCenterY
63 | relatedBy:NSLayoutRelationEqual
64 | toItem:contentView
65 | attribute:NSLayoutAttributeCenterY
66 | multiplier:1.0
67 | constant:0]];
68 |
69 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[titleLabel]|" options:0 metrics:nil views:views]];
70 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[indicator]-[titleLabel]" options:0 metrics:nil views:views]];
71 | }
72 |
73 | #pragma mark - Overridden Methods
74 |
75 | - (void)viewDidLoad
76 | {
77 | [super viewDidLoad];
78 | [self addContentViews];
79 | }
80 |
81 | @end
82 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSectionFooter.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSectionFooter.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 28/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MBSectionFooter : UIView
12 |
13 | @property (nonatomic, readonly, nullable) UIButton *topButton;
14 | @property (nonatomic, readonly, nullable) UIImageView *imageView;
15 | @property (nonatomic, readonly, nullable) UILabel *titleLabel;
16 | @property (nonatomic, readonly, nullable) UILabel *subtitleLabel;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSectionFooter.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSectionFooter.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 28/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSectionFooter.h"
10 |
11 | @interface MBSectionFooter ()
12 | @property (nonatomic) NSArray *customConstraints;
13 | @end
14 |
15 | static int MBSectionFooterContext;
16 |
17 | @implementation MBSectionFooter
18 |
19 | - (instancetype)initWithFrame:(CGRect)frame
20 | {
21 | self = [super initWithFrame:frame];
22 | if (self) {
23 | [self configure];
24 | }
25 | return self;
26 | }
27 |
28 | - (void)dealloc
29 | {
30 | [_topButton removeObserver:self forKeyPath:@"hidden" context:&MBSectionFooterContext];
31 | [_imageView removeObserver:self forKeyPath:@"hidden" context:&MBSectionFooterContext];
32 | [_titleLabel removeObserver:self forKeyPath:@"hidden" context:&MBSectionFooterContext];
33 | [_subtitleLabel removeObserver:self forKeyPath:@"hidden" context:&MBSectionFooterContext];
34 | }
35 |
36 | - (void)configure
37 | {
38 | self.clipsToBounds = YES;
39 |
40 | UIButton *topButton = [UIButton buttonWithType:UIButtonTypeSystem];
41 | topButton.translatesAutoresizingMaskIntoConstraints = NO;
42 | _topButton = topButton;
43 | [topButton addObserver:self forKeyPath:@"hidden" options:0 context:&MBSectionFooterContext];
44 | [self addSubview:topButton];
45 |
46 | UIImageView *imageView = [[UIImageView alloc] init];
47 | imageView.translatesAutoresizingMaskIntoConstraints = NO;
48 | [imageView addObserver:self forKeyPath:@"hidden" options:0 context:&MBSectionFooterContext];
49 | _imageView = imageView;
50 | [self addSubview:imageView];
51 |
52 | UILabel *titleLabel = [[UILabel alloc] init];
53 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
54 | titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
55 | titleLabel.textAlignment = NSTextAlignmentCenter;
56 | titleLabel.numberOfLines = 0;
57 | [titleLabel addObserver:self forKeyPath:@"hidden" options:0 context:&MBSectionFooterContext];
58 | _titleLabel = titleLabel;
59 | [self addSubview:titleLabel];
60 |
61 | UILabel *subtitleLabel = [[UILabel alloc] init];
62 | subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
63 | subtitleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
64 | subtitleLabel.textAlignment = NSTextAlignmentCenter;
65 | subtitleLabel.numberOfLines = 0;
66 | [subtitleLabel addObserver:self forKeyPath:@"hidden" options:0 context:&MBSectionFooterContext];
67 | _subtitleLabel = subtitleLabel;
68 | [self addSubview:subtitleLabel];
69 | }
70 |
71 | - (UIEdgeInsets)_layoutMargins
72 | {
73 | UIEdgeInsets margins = UIEdgeInsetsMake(15.0, 8.0, 8.0, 8.0);
74 | return margins;
75 | }
76 |
77 | #pragma mark - Overridden Methods
78 |
79 | - (void)updateConstraints
80 | {
81 | [super updateConstraints];
82 |
83 | NSDictionary *views = NSDictionaryOfVariableBindings(_topButton, _imageView, _titleLabel, _subtitleLabel);
84 |
85 | NSMutableArray *constraints = [NSMutableArray new];
86 |
87 | [constraints addObject:[NSLayoutConstraint constraintWithItem:_topButton
88 | attribute:NSLayoutAttributeCenterX
89 | relatedBy:NSLayoutRelationEqual
90 | toItem:self
91 | attribute:NSLayoutAttributeCenterX
92 | multiplier:1.0
93 | constant:0]];
94 |
95 | [constraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
96 | attribute:NSLayoutAttributeCenterX
97 | relatedBy:NSLayoutRelationEqual
98 | toItem:self
99 | attribute:NSLayoutAttributeCenterX
100 | multiplier:1.0
101 | constant:0]];
102 |
103 | UIEdgeInsets margins = self._layoutMargins;
104 |
105 | NSMutableArray *components = [NSMutableArray new];
106 |
107 | if (!self.topButton.hidden) {
108 | [components addObject:@"[_topButton]"];
109 | }
110 |
111 | if (!self.imageView.hidden) {
112 | [components addObject:@"[_imageView]"];
113 | }
114 |
115 | if (!self.titleLabel.hidden) {
116 | [components addObject:@"[_titleLabel]"];
117 | }
118 |
119 | if (!self.subtitleLabel.hidden) {
120 | [components addObject:@"[_subtitleLabel]"];
121 | }
122 |
123 | if (components.count > 0) {
124 |
125 | NSString *marginStr = [NSString stringWithFormat:@"-%i-", (int)margins.bottom];
126 | NSString *joinedComponents = [components componentsJoinedByString:marginStr];
127 |
128 | NSString *constraintsFormat = [NSMutableString stringWithFormat:@"V:|-%f-%@", margins.top, joinedComponents];
129 | [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:constraintsFormat options:0 metrics:nil views:views]];
130 | }
131 |
132 | NSString *format = [NSString stringWithFormat:@"H:|-%f-[_titleLabel]-%f-|", margins.left, margins.right];
133 | [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:views]];
134 |
135 | format = [NSString stringWithFormat:@"H:|-%f-[_subtitleLabel]-%f-|", margins.left, margins.right];
136 | [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:views]];
137 |
138 | _customConstraints = constraints;
139 | [self addConstraints:constraints];
140 | }
141 |
142 | - (CGSize)sizeThatFits:(CGSize)size
143 | {
144 | UIEdgeInsets margins = self._layoutMargins;
145 |
146 | CGFloat height = margins.top;
147 |
148 | if (!self.topButton.hidden) {
149 | CGFloat topButtonHeight = [self.topButton sizeThatFits:size].height;
150 | height += topButtonHeight + margins.bottom;
151 | }
152 |
153 | if (!self.imageView.hidden) {
154 | CGFloat imageViewHeight = [self.imageView sizeThatFits:size].height;
155 | height += imageViewHeight + margins.bottom;
156 | }
157 |
158 | CGSize sizeForLabels = size;
159 | sizeForLabels.width = size.width - (margins.left + margins.right);
160 |
161 | if (!self.titleLabel.hidden) {
162 | CGFloat titleLabelHeight = [self.titleLabel sizeThatFits:sizeForLabels].height;
163 | height += titleLabelHeight + margins.bottom;
164 | }
165 |
166 | if (!self.subtitleLabel.hidden) {
167 | CGFloat subtitleLabelHeight = [self.subtitleLabel sizeThatFits:sizeForLabels].height;
168 | height += subtitleLabelHeight;
169 | }
170 |
171 | return CGSizeMake(size.width, height);
172 | }
173 |
174 | #pragma mark - KVO Notifications
175 |
176 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
177 | {
178 | if (context == &MBSectionFooterContext) {
179 | if ([keyPath isEqualToString:@"hidden"]) {
180 | if (_customConstraints) {
181 | [self removeConstraints:_customConstraints];
182 | }
183 | [self setNeedsUpdateConstraints];
184 | }
185 | } else {
186 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
187 | }
188 | }
189 |
190 | @end
191 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSectionHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSectionHeader.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MBSectionHeader : UIView
12 |
13 | @property (nonatomic, readonly, nullable) UILabel *titleLabel;
14 |
15 | /**
16 | Title label's top and bottom insets
17 | */
18 | @property (nonatomic) UIEdgeInsets titleLabelInset;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSectionHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSectionHeader.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSectionHeader.h"
10 | #import "MBSetupControllerUtilities.h"
11 |
12 | @interface MBSectionHeader()
13 | @property (nonatomic) NSLayoutConstraint *labelTopMarginConstraint;
14 | @property (nonatomic) NSLayoutConstraint *labelBottomMarginConstraint;
15 | @end
16 |
17 | @implementation MBSectionHeader
18 |
19 | - (instancetype)initWithFrame:(CGRect)frame
20 | {
21 | self = [super initWithFrame:frame];
22 | if (self) {
23 | [self configure];
24 | }
25 | return self;
26 | }
27 |
28 | - (void)configure
29 | {
30 | _titleLabelInset = UIEdgeInsetsMake(0.0, 0, 10.0, 0);
31 |
32 | UILabel *label = [MBSetupControllerUtilities captionStyledLabel];
33 | label.translatesAutoresizingMaskIntoConstraints = NO;
34 | [self addSubview:label];
35 |
36 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(label)]];
37 |
38 | _labelTopMarginConstraint = [NSLayoutConstraint constraintWithItem:label
39 | attribute:NSLayoutAttributeTop
40 | relatedBy:NSLayoutRelationEqual
41 | toItem:self
42 | attribute:NSLayoutAttributeTop
43 | multiplier:1.0
44 | constant:_titleLabelInset.top];
45 | [self addConstraint:_labelTopMarginConstraint];
46 |
47 | _labelBottomMarginConstraint = [NSLayoutConstraint constraintWithItem:self
48 | attribute:NSLayoutAttributeBottom
49 | relatedBy:NSLayoutRelationGreaterThanOrEqual
50 | toItem:label
51 | attribute:NSLayoutAttributeBottom
52 | multiplier:1.0
53 | constant:_titleLabelInset.bottom];
54 | [self addConstraint:_labelBottomMarginConstraint];
55 |
56 | _titleLabel = label;
57 | }
58 |
59 | - (void)setTitleLabelInset:(UIEdgeInsets)inset
60 | {
61 | _titleLabelInset = inset;
62 | _labelTopMarginConstraint.constant = inset.top;
63 | _labelBottomMarginConstraint.constant = inset.bottom;
64 | }
65 |
66 | #pragma mark - Overridden Methods
67 |
68 | - (CGSize)sizeThatFits:(CGSize)size
69 | {
70 | CGSize fittingSize = [self.titleLabel sizeThatFits:size];
71 | fittingSize.height += _titleLabelInset.top + _titleLabelInset.bottom;
72 |
73 | return fittingSize;
74 | }
75 |
76 | @end
77 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @protocol MBSetupControllerDataSource;
14 | @protocol MBSetupControllerDelegate;
15 |
16 | #pragma mark - MBPage
17 |
18 | @protocol MBPage
19 | /**
20 | Indicates whether setup controller should remove the receiver from stack when its done (i.e. another page controller has been pushed on stack above it)
21 | */
22 | @property (nonatomic) BOOL removeWhenDone;
23 |
24 | /**
25 | Will be set to YES if receiver has been skipped.
26 | */
27 | @property (nonatomic, readonly, getter=isSkipped) BOOL skipped;
28 | @end
29 |
30 | #pragma mark - MBSetupController
31 |
32 | @interface MBSetupController : UIViewController
33 |
34 | @property (weak, nonatomic, nullable) id dataSource;
35 | @property (weak, nonatomic, nullable) id delegate;
36 |
37 | #pragma mark - Customizing Appearance
38 |
39 | @property (nonatomic, readonly) UINavigationController *setupNavigationController;
40 |
41 | #pragma mark - Providing Content
42 |
43 | /**
44 | The view controllers currently on stack.
45 | */
46 | @property (nonatomic, readonly) NSArray *> *viewControllers;
47 | - (void)setViewControllers:(NSArray *> *)controllers animated:(BOOL)animated;
48 |
49 | #pragma mark - Navigating
50 |
51 | /**
52 | Pop current view controller from stack.
53 | */
54 | - (void)popBack;
55 |
56 | /**
57 | Push next view controller on stack.
58 | */
59 | - (void)pushNext;
60 |
61 | @end
62 |
63 | #pragma mark - Protocols
64 |
65 | @protocol MBSetupControllerDataSource
66 | /**
67 | Returns the next page view controller after the given view controller.
68 | */
69 | - (nullable UIViewController *)setupController:(MBSetupController *)setupController viewControllerAfterViewController:(nullable UIViewController *)viewController;
70 | @end
71 |
72 | @protocol MBSetupControllerDelegate
73 | @optional
74 | - (void)setupController:(MBSetupController *)setupController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
75 | - (void)setupController:(MBSetupController *)setupController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
76 | - (void)setupControllerDidFinish:(MBSetupController *)setupController;
77 | @end
78 |
79 | NS_ASSUME_NONNULL_END
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 26/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupController.h"
10 | #import "MBTableViewPageController.h"
11 |
12 | @interface MBSetupController ()
13 | @end
14 |
15 | @implementation MBSetupController
16 |
17 | - (void)setNavController:(UINavigationController *)controller
18 | {
19 | if (_setupNavigationController == controller) {
20 | return;
21 | }
22 |
23 | [_setupNavigationController willMoveToParentViewController:nil];
24 | [_setupNavigationController.view removeFromSuperview];
25 | [_setupNavigationController removeFromParentViewController];
26 | _setupNavigationController.delegate = nil;
27 |
28 | _setupNavigationController = controller;
29 | controller.delegate = self;
30 |
31 | [self addChildViewController:controller];
32 |
33 | UIView *view = controller.view;
34 | view.translatesAutoresizingMaskIntoConstraints = NO;
35 |
36 | [self.view addSubview:view];
37 |
38 | NSDictionary *views = NSDictionaryOfVariableBindings(view);
39 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:views]];
40 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:views]];
41 |
42 | [controller didMoveToParentViewController:self];
43 | }
44 |
45 | - (UIImage *)imageWithColor:(UIColor *)color
46 | {
47 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
48 | UIGraphicsBeginImageContext(rect.size);
49 | CGContextRef context = UIGraphicsGetCurrentContext();
50 |
51 | CGContextSetFillColorWithColor(context, [color CGColor]);
52 | CGContextFillRect(context, rect);
53 |
54 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
55 | UIGraphicsEndImageContext();
56 |
57 | return image;
58 | }
59 |
60 | #pragma mark - Providing Content
61 |
62 | - (NSArray *)viewControllers
63 | {
64 | return self.setupNavigationController.viewControllers;
65 | }
66 |
67 | - (void)setViewControllers:(NSArray *)controllers animated:(BOOL)animated
68 | {
69 | [self.setupNavigationController setViewControllers:controllers animated:animated];
70 | }
71 |
72 | #pragma mark - Navigating
73 |
74 | - (void)popBack
75 | {
76 | [self.setupNavigationController popViewControllerAnimated:YES];
77 | }
78 |
79 | - (void)pushNext
80 | {
81 | UIViewController *topController = (UIViewController *)self.setupNavigationController.topViewController;
82 | UIViewController *nextController = [self.dataSource setupController:self viewControllerAfterViewController:topController];
83 | if (nextController) {
84 | [self.setupNavigationController pushViewController:nextController animated:YES];
85 | } else {
86 | //No more controllers to present, so setup is finished
87 | if ([self.delegate respondsToSelector:@selector(setupControllerDidFinish:)]) {
88 | [self.delegate setupControllerDidFinish:self];
89 | }
90 | }
91 | }
92 |
93 | #pragma mark - Overridden Methods
94 |
95 | - (void)viewDidLoad
96 | {
97 | [super viewDidLoad];
98 |
99 | UINavigationController *controller = [[UINavigationController alloc] init];
100 |
101 | //White navigation bar background
102 | UIImage *whiteImage = [self imageWithColor:[UIColor whiteColor]];
103 | [controller.navigationBar setBackgroundImage:whiteImage forBarMetrics:UIBarMetricsDefault];
104 | controller.navigationBar.shadowImage = [UIImage new];
105 |
106 | [self setNavController:controller];
107 | }
108 |
109 | - (void)viewWillAppear:(BOOL)animated
110 | {
111 | [super viewWillAppear:animated];
112 |
113 | if (self.viewControllers.count == 0) {
114 | UIViewController *initialViewController = [self.dataSource setupController:self viewControllerAfterViewController:nil];
115 | if (initialViewController) {
116 | [self setViewControllers:@[initialViewController] animated:NO];
117 | }
118 | }
119 | }
120 |
121 | #pragma mark - UINavigationControllerDelegate
122 |
123 | - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
124 | {
125 | if ([self.delegate respondsToSelector:@selector(setupController:willShowViewController:animated:)]) {
126 | [self.delegate setupController:self willShowViewController:viewController animated:animated];
127 | }
128 | }
129 |
130 | - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
131 | {
132 | if ([self.delegate respondsToSelector:@selector(setupController:didShowViewController:animated:)]) {
133 | [self.delegate setupController:self didShowViewController:viewController animated:animated];
134 | }
135 |
136 | UIViewController *previousViewController = nil;
137 | if (navigationController.viewControllers.count > 1) {
138 | previousViewController = (UIViewController *)navigationController.viewControllers[navigationController.viewControllers.count - 2];
139 |
140 | if (previousViewController.removeWhenDone) {
141 | NSMutableArray *controllers = [navigationController.viewControllers mutableCopy];
142 | [controllers removeObject:previousViewController];
143 | [navigationController setViewControllers:controllers animated:NO];
144 | }
145 | }
146 | }
147 |
148 | @end
149 |
150 | @implementation MBSetupController (Friend)
151 |
152 | - (void)pageControllerProceedToPreviousPage:(UIViewController *)controller
153 | {
154 | [self popBack];
155 | }
156 |
157 | - (void)pageControllerProceedToNextPage:(UIViewController *)controller
158 | {
159 | [self pushNext];
160 | }
161 |
162 | @end
163 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupControllerUtilities.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupControllerUtilities.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 30/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class MBSetupController;
14 |
15 | @interface MBSetupControllerUtilities : NSObject
16 | + (nullable UILabel *)captionStyledLabel;
17 | + (BOOL)isAutosizingTableViewCellsSupported;
18 | @end
19 |
20 | @interface UIViewController (MBSetupController)
21 |
22 | /**
23 | The nearest ancestor in view controller hierarchy that is setup controller.
24 | */
25 | @property (nonatomic, readonly, nullable) MBSetupController *mbSetupController;
26 |
27 | @end
28 |
29 | @interface UIView (MBFirstResponder)
30 | - (nullable id)mbFindFirstResponder;
31 | @end
32 |
33 | @interface UIViewController (MBSizeClasses)
34 | /**
35 | Returns YES if current trait horizontal size class is regular
36 | */
37 | - (BOOL)mbIsRegularHorizontalSizeClass;
38 | - (BOOL)mbIsRegularVerticalSizeClass;
39 | @end
40 |
41 | NS_ASSUME_NONNULL_END
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupControllerUtilities.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupControllerUtilities.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 30/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupControllerUtilities.h"
10 | #import "MBSetupController.h"
11 |
12 | @implementation MBSetupControllerUtilities
13 |
14 | + (UILabel *)captionStyledLabel
15 | {
16 | UILabel *label = [[UILabel alloc] init];
17 | label.font = [UIFont fontWithName:@"HelveticaNeue-Thin" size:30];
18 | label.textAlignment = NSTextAlignmentCenter;
19 | label.numberOfLines = 0;
20 |
21 | return label;
22 | }
23 |
24 | + (BOOL)isAutosizingTableViewCellsSupported
25 | {
26 | //We are on iOS8 or later
27 | return [UITraitCollection class] != nil;
28 | }
29 |
30 | @end
31 |
32 | @implementation UIViewController (SetupController)
33 |
34 | - (MBSetupController *)mbSetupController
35 | {
36 | UIViewController *parent = self.parentViewController;
37 | if ([parent isKindOfClass:[MBSetupController class]]) {
38 | return (MBSetupController *)parent;
39 | }
40 |
41 | return parent.mbSetupController;
42 | }
43 |
44 | @end
45 |
46 | @implementation UIView (MBFirstResponder)
47 |
48 | - (id)mbFindFirstResponder
49 | {
50 | if (self.isFirstResponder) {
51 | return self;
52 | }
53 | for (UIView *subView in self.subviews) {
54 | id responder = [subView mbFindFirstResponder];
55 | if (responder) return responder;
56 | }
57 | return nil;
58 | }
59 |
60 | @end
61 |
62 | @implementation UIViewController (MBSizeClasses)
63 |
64 | - (BOOL)mbIsRegularHorizontalSizeClass
65 | {
66 | BOOL isRegular = NO;
67 |
68 | if ([UITraitCollection class]) {
69 | //iOS 8 or later
70 | UITraitCollection *traits = self.traitCollection;
71 | isRegular = traits.horizontalSizeClass == UIUserInterfaceSizeClassRegular;
72 | } else {
73 | //iOS 7 or earlier
74 | isRegular = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
75 | }
76 |
77 | return isRegular;
78 | }
79 |
80 | - (BOOL)mbIsRegularVerticalSizeClass
81 | {
82 | BOOL isRegular = NO;
83 |
84 | if ([UITraitCollection class]) {
85 | //iOS 8 or later
86 | UITraitCollection *traits = self.traitCollection;
87 | isRegular = traits.verticalSizeClass == UIUserInterfaceSizeClassRegular;
88 | } else {
89 | //iOS 7 or earlier
90 | isRegular = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
91 | }
92 |
93 | return isRegular;
94 | }
95 |
96 | @end
97 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupPageCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageCell.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class MBSetupPageItem;
12 |
13 | @interface MBSetupPageCell : UITableViewCell
14 |
15 | @property (nonatomic, nullable) MBSetupPageItem *item;
16 | @property (nonatomic) UITableViewCellSeparatorStyle customSeparatorStyle;
17 |
18 | /**
19 | Called just after the cell is created.
20 | */
21 | - (void)cellDidLoad;
22 |
23 | /**
24 | Called when cell is about to be presented.
25 | */
26 | - (void)cellWillAppear;
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupPageCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageCell.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageCell.h"
10 | #import "MBSetupPageItem.h"
11 | #import "MBSetupControllerUtilities.h"
12 |
13 | @interface MBSetupPageCell ()
14 | @property (nonatomic) UIView *separatorView;
15 | @end
16 |
17 | @implementation MBSetupPageCell
18 |
19 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
20 | {
21 | self = [super initWithCoder:aDecoder];
22 | if (self) {
23 | [self _cellDidLoad];
24 | }
25 | return self;
26 | }
27 |
28 | - (instancetype)initWithFrame:(CGRect)frame
29 | {
30 | self = [super initWithFrame:frame];
31 | if (self) {
32 | [self _cellDidLoad];
33 | }
34 | return self;
35 | }
36 |
37 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
38 | {
39 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
40 | if (self) {
41 | [self _cellDidLoad];
42 | }
43 | return self;
44 | }
45 |
46 | - (void)dealloc
47 | {
48 | // Set item to nil to unregister from all KVO notifications
49 | [self setItem:nil];
50 | }
51 |
52 | - (void)setCustomSeparatorStyle:(UITableViewCellSeparatorStyle)customSeparatorStyle {
53 | if (_customSeparatorStyle != customSeparatorStyle) {
54 | _customSeparatorStyle = customSeparatorStyle;
55 | [self updateSeparatorView];
56 | }
57 | }
58 |
59 | - (UIView *)addSeparatorView
60 | {
61 | if (!self.backgroundView) {
62 | UIView *view = [[UIView alloc] initWithFrame:self.bounds];
63 | self.backgroundView = view;
64 | }
65 |
66 | UIView *separatorSuperview = self.backgroundView;
67 |
68 | UIView *separatorView = [[UIView alloc] init];
69 | separatorView.translatesAutoresizingMaskIntoConstraints = NO;
70 | separatorView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.2];
71 | [separatorSuperview addSubview:separatorView];
72 |
73 | [separatorSuperview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[separatorView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(separatorView)]];
74 | [separatorSuperview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[separatorView(0.5)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(separatorView)]];
75 |
76 | return separatorView;
77 | }
78 |
79 | - (void)updateSeparatorView
80 | {
81 | [_separatorView removeFromSuperview];
82 | if ([self customSeparatorStyle] != UITableViewCellSeparatorStyleNone) {
83 | _separatorView = [self addSeparatorView];
84 | }
85 | }
86 |
87 | - (void)_cellDidLoad
88 | {
89 | [self updateSeparatorView];
90 |
91 | if ([MBSetupControllerUtilities isAutosizingTableViewCellsSupported]) {
92 | //Add minimal height constraint, to prevent that tableview complains about possible zero height
93 | //if cell does not define enough constraints to compute cell's height
94 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView
95 | attribute:NSLayoutAttributeHeight
96 | relatedBy:NSLayoutRelationGreaterThanOrEqual
97 | toItem:nil
98 | attribute:0
99 | multiplier:1.0
100 | constant:44.0]];
101 | }
102 |
103 | [self cellDidLoad];
104 | }
105 |
106 | - (void)cellDidLoad
107 | {
108 | }
109 |
110 | - (void)cellWillAppear
111 | {
112 | }
113 |
114 | @end
115 |
116 | @implementation MBSetupPageCell (Friend)
117 |
118 | - (void)_cellWillAppear
119 | {
120 | self.selectionStyle = self.item.selectionStyle;
121 | self.accessoryType = self.item.accessoryType;
122 |
123 | [self cellWillAppear];
124 | }
125 |
126 | @end
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupPageItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageItem.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class MBSetupPageItem;
14 | @class MBSetupPageCell;
15 | @class MBSetupPageSection;
16 |
17 | typedef CGFloat(^MBSetupPageItemCellHeightBlock)(UITableView *tableView, MBSetupPageItem *item, MBSetupPageCell *cell);
18 |
19 | @interface MBSetupPageItem : NSObject
20 |
21 | - (instancetype)initWithTitle:(nullable NSString *)title;
22 |
23 | @property (nonatomic, nullable) NSString *identifier;
24 | @property (nonatomic, nullable) NSString *title;
25 |
26 | /**
27 | Cell identifier.
28 | */
29 | @property (nonatomic, nullable) NSString *cellIdentifier;
30 |
31 | /**
32 | Parent section
33 | */
34 | @property (weak, nonatomic, readonly, nullable) MBSetupPageSection *section;
35 |
36 | #pragma mark - Configure Cell Appearance
37 |
38 | @property (nonatomic) UITableViewCellSelectionStyle selectionStyle;
39 | @property (nonatomic) UITableViewCellAccessoryType accessoryType;
40 |
41 | /**
42 | Block to create table view cell.
43 | */
44 | @property (copy, nonatomic, nullable) MBSetupPageCell *(^createCellBlock)(MBSetupPageItem *item);
45 |
46 | /**
47 | Block to configure table view cell.
48 | */
49 | @property (copy, nonatomic, nullable) void(^configureCellBlock)(MBSetupPageItem *item, MBSetupPageCell *cell);
50 |
51 | /**
52 | * Block called after configureCellBlock.
53 | */
54 | @property (copy, nonatomic, nullable) void(^afterConfigureCellBlock)(MBSetupPageItem *item, MBSetupPageCell *cell);
55 |
56 | /**
57 | Block to return height of the cell.
58 | */
59 | @property (copy, nonatomic, nullable) MBSetupPageItemCellHeightBlock cellHeightBlock;
60 |
61 | /**
62 | Item validation block
63 | */
64 | @property (copy, nonatomic, nullable) BOOL(^validateBlock)(MBSetupPageItem *item);
65 |
66 | #pragma mark - Handling Selection
67 |
68 | /**
69 | Block called on the item's row selection.
70 | */
71 | @property (copy, nonatomic, nullable) void(^didSelectBlock)(MBSetupPageItem *item);
72 |
73 | - (void)deselectAnimated:(BOOL)animated;
74 |
75 | @end
76 |
77 | NS_ASSUME_NONNULL_END
78 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupPageItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageItem.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageItem.h"
10 | #import "MBSetupPageCell.h"
11 | #import "MBSetupPageSection.h"
12 |
13 | @interface MBSetupPageItem ()
14 | @property (weak, nonatomic) MBSetupPageSection *section;
15 | @end
16 |
17 | @implementation MBSetupPageItem
18 |
19 | - (instancetype)initWithTitle:(NSString *)title
20 | {
21 | self = [super init];
22 | if (self) {
23 | _title = title;
24 | _selectionStyle = UITableViewCellSelectionStyleDefault;
25 | _accessoryType = UITableViewCellAccessoryNone;
26 | }
27 | return self;
28 | }
29 |
30 | - (void)deselectAnimated:(BOOL)animated
31 | {
32 | [self.section deselectItem:self animated:animated];
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupPageSection.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageSection.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class MBTableViewPageController;
14 | @class MBSetupPageSection;
15 | @class MBSetupPageItem;
16 |
17 | typedef CGFloat(^MBSetupPageSectionViewHeightBlock)(UITableView *tableView, MBSetupPageSection *section, UIView *view);
18 |
19 | @interface MBSetupPageSection : NSObject
20 |
21 | - (instancetype)initWithTitle:(nullable NSString *)title;
22 | + (instancetype)sectionWithTitle:(nullable NSString *)title;
23 |
24 | /**
25 | Receiver's title.
26 | */
27 | @property (nonatomic, nullable) NSString *title;
28 |
29 | /**
30 | Block to return header view for the receiver.
31 | */
32 | @property (copy, nonatomic, nullable) UIView *(^headerViewBlock)(MBSetupPageSection *section);
33 |
34 | /**
35 | Block to return header view height.
36 | */
37 | @property (copy, nonatomic, nullable) MBSetupPageSectionViewHeightBlock headerHeightBlock;
38 |
39 | /**
40 | Block to return footer view for the receiver.
41 | */
42 | @property (copy, nonatomic, nullable) UIView *(^footerViewBlock)(MBSetupPageSection *section);
43 |
44 | /**
45 | Block to return footer view height.
46 | */
47 | @property (copy, nonatomic, nullable) MBSetupPageSectionViewHeightBlock footerHeightBlock;
48 |
49 | #pragma mark - Parent Controller
50 |
51 | @property (weak, nonatomic, readonly, nullable) MBTableViewPageController *parentController;
52 |
53 | #pragma mark - Providing Section Items
54 |
55 | /**
56 | Section row items.
57 | */
58 | @property (nonatomic) NSArray *items;
59 |
60 | - (void)insertItem:(MBSetupPageItem *)item atIndex:(NSInteger)index;
61 |
62 | #pragma mark - Validation
63 |
64 | /**
65 | Validate items.
66 | */
67 | - (BOOL)validate;
68 |
69 | #pragma mark - Handling Selection
70 |
71 | - (void)deselectItem:(MBSetupPageItem *)item animated:(BOOL)animated;
72 |
73 | @end
74 |
75 | NS_ASSUME_NONNULL_END
76 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSetupPageSection.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageSection.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageSection.h"
10 | #import "MBSetupPageItem.h"
11 | #import "MBTableViewPageController.h"
12 |
13 | @interface MBSetupPageItem (Friend)
14 | @property (weak, nonatomic) MBSetupPageSection *section;
15 | @end
16 |
17 | @interface MBSetupPageSection ()
18 | @property (weak, nonatomic) MBTableViewPageController *parentController;
19 | @property (nonatomic) NSMutableArray *mutableItems;
20 | @end
21 |
22 | @implementation MBSetupPageSection
23 |
24 | - (instancetype)initWithTitle:(NSString *)title
25 | {
26 | self = [super init];
27 | if (self) {
28 | _title = title;
29 | _mutableItems = [NSMutableArray new];
30 | }
31 | return self;
32 | }
33 |
34 | + (instancetype)sectionWithTitle:(NSString *)title
35 | {
36 | return [[self alloc] initWithTitle:title];
37 | }
38 |
39 | #pragma mark - Providing Section Items
40 |
41 | - (NSArray *)items
42 | {
43 | return [_mutableItems copy];
44 | }
45 |
46 | - (void)setItems:(NSArray *)items
47 | {
48 | _mutableItems = [items mutableCopy];
49 |
50 | for (MBSetupPageItem *each in _mutableItems) {
51 | each.section = self;
52 | }
53 | }
54 |
55 | - (void)insertItem:(MBSetupPageItem *)item atIndex:(NSInteger)index
56 | {
57 | NSInteger sectionIndex = [self.parentController.sections indexOfObject:self];
58 | [_mutableItems insertObject:item atIndex:index];
59 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:sectionIndex];
60 | [self.parentController.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
61 | }
62 |
63 | - (NSIndexPath *)indexPathForItem:(MBSetupPageItem *)item
64 | {
65 | NSInteger sectionIndex = [self.parentController.sections indexOfObject:self];
66 | NSInteger itemIndex = [self.items indexOfObject:item];
67 |
68 | if (sectionIndex == NSNotFound || itemIndex == NSNotFound) {
69 | return nil;
70 | }
71 |
72 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:itemIndex inSection:sectionIndex];
73 | return indexPath;
74 | }
75 |
76 | #pragma mark - Validation
77 |
78 | - (BOOL)validate
79 | {
80 | BOOL success = YES;
81 | for (MBSetupPageItem *each in self.items) {
82 | if (each.validateBlock) {
83 | success &= each.validateBlock(each);
84 | if (!success) { break; }
85 | }
86 | }
87 |
88 | return success;
89 | }
90 |
91 | #pragma mark - Handling Selection
92 |
93 | - (void)deselectItem:(MBSetupPageItem *)item animated:(BOOL)animated
94 | {
95 | NSIndexPath *indexPath = [self indexPathForItem:item];
96 | [self.parentController.tableView deselectRowAtIndexPath:indexPath animated:animated];
97 | }
98 |
99 | @end
100 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSwitchCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSwitchCell.h
3 | // SetupController
4 | //
5 |
6 | #import "MBSetupPageCell.h"
7 | #import "MBSwitchItem.h"
8 |
9 | @interface MBSwitchCell : MBSetupPageCell
10 |
11 | @property (nonatomic, readonly, nullable) UILabel *titleLabel;
12 | @property (nonatomic, readonly, nullable) UISwitch *switchView;
13 |
14 | @property (nonatomic, nullable) MBSwitchItem *item;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSwitchCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSwitchCell.m
3 | // SetupController
4 | //
5 |
6 | #import "MBSwitchCell.h"
7 | #import "MBSwitchItem.h"
8 |
9 | static int MBSwitchCellContext;
10 |
11 | #define DEFAULT_MIN_TITLE_LABEL_WIDTH 110
12 |
13 | @interface MBSwitchCell()
14 | @property (nonatomic) BOOL updatingItemText;
15 | @property (nonatomic) NSArray *horizontalSwitchConstraints;
16 | @end
17 |
18 | @implementation MBSwitchCell
19 |
20 | @dynamic item;
21 |
22 | - (void)dealloc
23 | {
24 | [_switchView removeTarget:self action:@selector(switchDidChange:) forControlEvents:UIControlEventValueChanged];
25 | }
26 |
27 | - (void)switchDidChange: (id) sender
28 | {
29 | MBSwitchItem *item = (MBSwitchItem *)self.item;
30 | item.value = _switchView.on;
31 | }
32 |
33 | - (void)updateSwitchViewConstraints
34 | {
35 | if (_horizontalSwitchConstraints) {
36 | [self.contentView removeConstraints:_horizontalSwitchConstraints];
37 | [self setNeedsUpdateConstraints];
38 | }
39 | }
40 |
41 | #pragma mark - Overridden Methods
42 |
43 | - (void)setItem:(MBSwitchItem *)item
44 | {
45 | if (self.item != item) {
46 |
47 | [self.item removeObserver:self forKeyPath:NSStringFromSelector(@selector(value)) context:&MBSwitchCellContext];
48 | [self.item removeObserver:self forKeyPath:NSStringFromSelector(@selector(switchAlignment)) context:&MBSwitchCellContext];
49 |
50 | [super setItem:item];
51 |
52 | [self.item addObserver:self forKeyPath:NSStringFromSelector(@selector(value)) options:0 context:&MBSwitchCellContext];
53 | [self.item addObserver:self forKeyPath:NSStringFromSelector(@selector(switchAlignment)) options:0 context:&MBSwitchCellContext];
54 | }
55 | }
56 |
57 | - (void)cellDidLoad
58 | {
59 | [super cellDidLoad];
60 |
61 | UIView *contentView = self.contentView;
62 |
63 | UILabel *titleLabel = [[UILabel alloc] init];
64 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
65 | [titleLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
66 | titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
67 | [contentView addSubview:titleLabel];
68 |
69 | UISwitch *switchView = [[UISwitch alloc] init];
70 | switchView.translatesAutoresizingMaskIntoConstraints = NO;
71 | [contentView addSubview:switchView];
72 |
73 | NSDictionary *views = NSDictionaryOfVariableBindings(titleLabel);
74 |
75 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[titleLabel]" options:0 metrics:nil views:views]];
76 |
77 | [titleLabel addConstraint:[NSLayoutConstraint constraintWithItem:titleLabel
78 | attribute:NSLayoutAttributeWidth
79 | relatedBy:NSLayoutRelationGreaterThanOrEqual
80 | toItem:nil
81 | attribute:0
82 | multiplier:1.0
83 | constant:DEFAULT_MIN_TITLE_LABEL_WIDTH]];
84 |
85 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:contentView
86 | attribute:NSLayoutAttributeCenterY
87 | relatedBy:NSLayoutRelationEqual
88 | toItem:titleLabel
89 | attribute:NSLayoutAttributeCenterY
90 | multiplier:1.0
91 | constant:0.0]];
92 |
93 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:contentView
94 | attribute:NSLayoutAttributeCenterY
95 | relatedBy:NSLayoutRelationEqual
96 | toItem:switchView
97 | attribute:NSLayoutAttributeCenterY
98 | multiplier:1.0
99 | constant:0.0]];
100 |
101 | _titleLabel = titleLabel;
102 | _switchView = switchView;
103 |
104 | [switchView addTarget:self action:@selector(switchDidChange:) forControlEvents:UIControlEventValueChanged];
105 | }
106 |
107 | - (void)cellWillAppear
108 | {
109 | [super cellWillAppear];
110 |
111 | MBSwitchItem *item = (MBSwitchItem *)self.item;
112 |
113 | self.titleLabel.text = item.title;
114 | self.switchView.on = item.value;
115 |
116 | // Switch alignment may have been changed so we have to update constraints
117 | [self updateSwitchViewConstraints];
118 | }
119 |
120 | - (void)updateConstraints
121 | {
122 | [super updateConstraints];
123 |
124 | NSMutableArray *hSwitchConstraints = [NSMutableArray new];
125 |
126 | UILabel *titleLabel = self.titleLabel;
127 | UISwitch *switchView = self.switchView;
128 |
129 | NSDictionary *views = NSDictionaryOfVariableBindings(titleLabel, switchView);
130 |
131 | if (self.item.switchAlignment == MBSwitchAlignmentLeft) {
132 | [hSwitchConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"[titleLabel]-[switchView]" options:0 metrics:nil views:views]];
133 | } else {
134 | [hSwitchConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"[titleLabel]-(>=8)-[switchView]-|" options:0 metrics:nil views:views]];
135 | }
136 |
137 | [self.contentView addConstraints:hSwitchConstraints];
138 | _horizontalSwitchConstraints = hSwitchConstraints;
139 | }
140 |
141 | #pragma mark - KVO Notifications
142 |
143 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
144 | {
145 | if (context == &MBSwitchCellContext) {
146 | MBSwitchItem *item = (MBSwitchItem *)self.item;
147 |
148 | if ([keyPath isEqualToString:NSStringFromSelector(@selector(value))]) {
149 | //Items text did change
150 | if (!_updatingItemText) {
151 | self.switchView.on = item.value;
152 | }
153 | } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(switchAlignment))]) {
154 | [self updateSwitchViewConstraints];
155 | [self setNeedsUpdateConstraints];
156 | }
157 |
158 | } else {
159 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
160 | }
161 | }
162 |
163 | @end
164 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBSwitchItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSwitchItem.h
3 | // SetupController
4 | //
5 |
6 | #import "MBSetupPageItem.h"
7 |
8 | NS_ASSUME_NONNULL_BEGIN
9 |
10 | typedef NS_ENUM(NSInteger, MBSwitchAlignment) {
11 | MBSwitchAlignmentLeft = 0,
12 | MBSwitchAlignmentRight
13 | };
14 |
15 | @interface MBSwitchItem : MBSetupPageItem
16 |
17 | - (instancetype)initWithTitle:(nullable NSString *)title value:(BOOL)value;
18 |
19 | @property (nonatomic) BOOL value;
20 | @property (nonatomic) MBSwitchAlignment switchAlignment; // MBSwitchAlignmentRight is the default value.
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
--------------------------------------------------------------------------------
/SetupController/Classes/MBSwitchItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSwitchItem.m
3 | // SetupController
4 | //
5 |
6 | #import "MBSwitchItem.h"
7 | #import "MBSwitchCell.h"
8 |
9 | @implementation MBSwitchItem
10 |
11 | - (instancetype)initWithTitle:(NSString *)title value:(BOOL)value
12 | {
13 | self = [super initWithTitle:title];
14 | if (self)
15 | {
16 | _switchAlignment = MBSwitchAlignmentRight;
17 | self.value = value;
18 |
19 | //Make text field cell non-selectable
20 | self.selectionStyle = UITableViewCellSelectionStyleNone;
21 |
22 | self.createCellBlock = ^MBSetupPageCell *(MBSetupPageItem *item) {
23 | MBSwitchCell *cell = [[MBSwitchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:item.cellIdentifier];
24 | return cell;
25 | };
26 |
27 | self.configureCellBlock = ^(MBSetupPageItem *item, MBSetupPageCell *cell) {
28 | cell.item = item;
29 | };
30 |
31 | self.cellHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageItem *item, MBSetupPageCell *cell) {
32 | cell.item = item;
33 | CGSize fittingSize = CGSizeMake(tableView.bounds.size.width, 0);
34 | CGSize size = [cell sizeThatFits:fittingSize];
35 | return size.height;
36 | };
37 | }
38 | return self;
39 | }
40 |
41 | - (NSString *)cellIdentifier
42 | {
43 | return @"MBSwitchCell";
44 | }
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBTableViewPageController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageController.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBBasePageController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class MBSectionHeader;
14 | @class MBSectionFooter;
15 | @class MBSetupPageSection;
16 |
17 | @interface MBTableViewPageController : MBBasePageController
18 |
19 | @property (nonatomic, readonly, nullable) UITableView *tableView;
20 |
21 | #pragma mark - Customizing Appearance
22 |
23 | @property (nonatomic) BOOL useAutosizingCells;
24 |
25 | /**
26 | Apple does not allow per-cell separator customization, so we have to do it manually.
27 | One then completely disables the standard UITableView separators and returns something
28 | different than UITableViewCellSeparatorStyleNone for this property to add custom separator view in the cells.
29 | */
30 | @property (nonatomic, readonly) UITableViewCellSeparatorStyle customCellSeparatorStyle;
31 |
32 | - (UIEdgeInsets)tableViewContentInsetByAccountingForKeyboardFrame:(CGRect)keyboardFrame;
33 |
34 | #pragma mark - Providing Table View Content
35 |
36 | @property (nonatomic) NSArray *sections;
37 |
38 | /**
39 | Convenient method to create page header view with specified title.
40 | */
41 | - (MBSectionHeader *)preparedPageHeaderViewWithTitle:(nullable NSString *)title;
42 | - (MBSectionFooter *)preparedFooterViewWithImage:(nullable UIImage *)image title:(nullable NSString *)title subtitle:(nullable NSString *)subtitle;
43 |
44 | #pragma mark - Uer Input Validation
45 |
46 | - (BOOL)validate;
47 |
48 | @end
49 |
50 | NS_ASSUME_NONNULL_END
51 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBTableViewPageController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBSetupPageController.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBTableViewPageController.h"
10 | #import "MBSectionHeader.h"
11 | #import "MBSectionFooter.h"
12 | #import "MBSetupPageSection.h"
13 | #import "MBSetupPageItem.h"
14 | #import "MBSetupPageCell.h"
15 | #import "MBSetupControllerUtilities.h"
16 |
17 | #define MINIMUM_CELL_HEIGHT 44.0
18 | #define MINIMUM_HEADER_HEIGHT 22.0
19 | #define MINIMUM_FOOTER_HEIGHT 22.0
20 |
21 | #define HEADER_VIEW_LABEL_REGULAR_CLASS_MARGIN 60.0
22 |
23 | @interface MBSetupPageSection (Friend)
24 | @property (weak, nonatomic) MBBasePageController *parentController;
25 | @end
26 |
27 | @interface MBSetupPageCell (Friend)
28 | - (void)_cellWillAppear;
29 | @end
30 |
31 | @interface MBTableViewPageController ()
32 | @property (nonatomic) NSMutableArray *mutableSections;
33 | @property (nonatomic) UITextField *activeField;
34 | @end
35 |
36 | @implementation MBTableViewPageController
37 |
38 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
39 | {
40 | self = [super initWithCoder:aDecoder];
41 | if (self) {
42 | [self configurePageController];
43 | }
44 | return self;
45 | }
46 |
47 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
48 | {
49 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
50 | if (self) {
51 | [self configurePageController];
52 | }
53 | return self;
54 | }
55 |
56 | - (void)dealloc
57 | {
58 | [[NSNotificationCenter defaultCenter] removeObserver:self];
59 | }
60 |
61 | - (void)configurePageController
62 | {
63 | _useAutosizingCells = [MBSetupControllerUtilities isAutosizingTableViewCellsSupported];
64 |
65 | [[NSNotificationCenter defaultCenter] addObserver:self
66 | selector:@selector(keyboardDidShowNotification:)
67 | name:UIKeyboardDidShowNotification object:nil];
68 |
69 | [[NSNotificationCenter defaultCenter] addObserver:self
70 | selector:@selector(keyboardWillHideNotification:)
71 | name:UIKeyboardWillHideNotification object:nil];
72 |
73 | [[NSNotificationCenter defaultCenter] addObserver:self
74 | selector:@selector(textFieldDidBeginEditingNotification:)
75 | name:UITextFieldTextDidBeginEditingNotification object:nil];
76 |
77 | [[NSNotificationCenter defaultCenter] addObserver:self
78 | selector:@selector(textFieldDidEndEditingNotification:)
79 | name:UITextFieldTextDidEndEditingNotification object:nil];
80 | }
81 |
82 | - (void)addTableView
83 | {
84 | UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
85 | tableView.translatesAutoresizingMaskIntoConstraints = NO;
86 | tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
87 | tableView.backgroundColor = [UIColor whiteColor];
88 | tableView.estimatedRowHeight = 44.0;
89 | tableView.rowHeight = UITableViewAutomaticDimension;
90 |
91 | //Turn off default separators because table adds separator also above first row and we don't want to have it.
92 | //Custom separators are added manually during cell creation.
93 | tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
94 |
95 | UIView *contentView = self.contentView;
96 |
97 | [contentView addSubview:tableView];
98 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tableView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(tableView)]];
99 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[tableView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(tableView)]];
100 |
101 | tableView.dataSource = self;
102 | tableView.delegate = self;
103 |
104 | _tableView = tableView;
105 | }
106 |
107 | - (MBSectionHeader *)preparedPageHeaderViewWithTitle:(NSString *)title
108 | {
109 | MBSectionHeader *view = [[MBSectionHeader alloc] init];
110 | view.titleLabel.text = title;
111 |
112 | if (self.mbIsRegularVerticalSizeClass && self.mbIsRegularHorizontalSizeClass) {
113 | //We have a lot of space when in a regular size class, so push the things a bit apart
114 | view.titleLabelInset = UIEdgeInsetsMake(HEADER_VIEW_LABEL_REGULAR_CLASS_MARGIN, 0, HEADER_VIEW_LABEL_REGULAR_CLASS_MARGIN, 0);
115 | }
116 |
117 | return view;
118 | }
119 |
120 | - (MBSectionFooter *)preparedFooterViewWithImage:(UIImage *)image title:(NSString *)title subtitle:(NSString *)subtitle
121 | {
122 | MBSectionFooter *footer = [[MBSectionFooter alloc] init];
123 |
124 | footer.imageView.image = image;
125 | footer.imageView.hidden = image == nil;
126 |
127 | footer.titleLabel.text = title;
128 | footer.titleLabel.hidden = title.length == 0;
129 |
130 | footer.subtitleLabel.text = subtitle;
131 | footer.subtitleLabel.hidden = subtitle.length == 0;
132 |
133 | return footer;
134 | }
135 |
136 | - (UITableViewCellSeparatorStyle)customCellSeparatorStyle {
137 | return UITableViewCellSeparatorStyleSingleLine;
138 | }
139 |
140 | #pragma mark - Providing Table View Content
141 |
142 | - (NSArray *)sections
143 | {
144 | return [_mutableSections copy];
145 | }
146 |
147 | - (void)setSections:(NSArray *)sections
148 | {
149 | for (MBSetupPageSection *each in sections) {
150 | each.parentController = self;
151 | }
152 |
153 | _mutableSections = [sections mutableCopy];
154 | [self.tableView reloadData];
155 | }
156 |
157 | - (MBSetupPageItem *)itemAtIndexPath:(NSIndexPath *)indexPath
158 | {
159 | MBSetupPageSection *section = _mutableSections[indexPath.section];
160 | MBSetupPageItem *item = section.items[indexPath.row];
161 |
162 | return item;
163 | }
164 |
165 | #pragma mark - Validation
166 |
167 | - (BOOL)validate
168 | {
169 | BOOL success = YES;
170 |
171 | for (MBSetupPageSection *each in self.sections) {
172 | success &= each.validate;
173 | }
174 |
175 | return success;
176 | }
177 |
178 | #pragma mark - Overridden Methods
179 |
180 | - (void)viewDidLoad
181 | {
182 | [super viewDidLoad];
183 |
184 | self.view.backgroundColor = [UIColor whiteColor];
185 | [self addTableView];
186 | }
187 |
188 | - (void)viewWillAppear:(BOOL)animated
189 | {
190 | [super viewWillAppear:animated];
191 | [self validate];
192 | }
193 |
194 | - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
195 | {
196 | [super traitCollectionDidChange:previousTraitCollection];
197 | [self.tableView reloadData];
198 | }
199 |
200 | #pragma mark - UITableViewDataSource
201 |
202 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
203 | {
204 | return _mutableSections.count;
205 | }
206 |
207 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex
208 | {
209 | MBSetupPageSection *section = _mutableSections[sectionIndex];
210 | return section.items.count;
211 | }
212 |
213 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
214 | {
215 | MBSetupPageItem *item = [self itemAtIndexPath:indexPath];
216 | NSAssert(item.cellIdentifier != nil, @"cellIdentifier != nil not satisfied");
217 |
218 | MBSetupPageCell *cell = [tableView dequeueReusableCellWithIdentifier:item.cellIdentifier];
219 | if (!cell) {
220 | cell = item.createCellBlock(item);
221 | }
222 |
223 | cell.customSeparatorStyle = [self customCellSeparatorStyle];
224 | item.configureCellBlock(item, cell);
225 |
226 | if (item.afterConfigureCellBlock) {
227 | item.afterConfigureCellBlock(item, cell);
228 | }
229 |
230 | [cell _cellWillAppear];
231 |
232 | return cell;
233 | }
234 |
235 | #pragma mark - UITableViewDelegate
236 |
237 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)sectionIndex
238 | {
239 | MBSetupPageSection *section = _mutableSections[sectionIndex];
240 |
241 | UIView *view = nil;
242 | if (section.headerViewBlock) {
243 | view = section.headerViewBlock(section);
244 | }
245 |
246 | CGFloat height = 0;
247 |
248 | if (view && section.headerHeightBlock) {
249 | height = section.headerHeightBlock(tableView, section, view);
250 | }
251 |
252 | if (height == 0) {
253 | height = MINIMUM_HEADER_HEIGHT;
254 | }
255 |
256 | return height;
257 | }
258 |
259 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)sectionIndex
260 | {
261 | MBSetupPageSection *section = _mutableSections[sectionIndex];
262 |
263 | UIView *view = nil;
264 | if (section.footerViewBlock) {
265 | view = section.footerViewBlock(section);
266 | }
267 |
268 | CGFloat height = 0;
269 |
270 | if (view && section.footerHeightBlock) {
271 | height = section.footerHeightBlock(tableView, section, view);
272 | }
273 |
274 | if (height == 0) {
275 | height = MINIMUM_FOOTER_HEIGHT;
276 | }
277 |
278 | return height;
279 | }
280 |
281 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)sectionIndex
282 | {
283 | MBSetupPageSection *section = _mutableSections[sectionIndex];
284 |
285 | UIView *view = nil;
286 | if (section.headerViewBlock) {
287 | view = section.headerViewBlock(section);
288 | }
289 |
290 | return view;
291 | }
292 |
293 | - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)sectionIndex
294 | {
295 | MBSetupPageSection *section = _mutableSections[sectionIndex];
296 |
297 | UIView *view = nil;
298 | if (section.footerViewBlock) {
299 | view = section.footerViewBlock(section);
300 | }
301 |
302 | return view;
303 | }
304 |
305 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
306 | {
307 | if (self.useAutosizingCells) {
308 | return UITableViewAutomaticDimension;
309 | }
310 |
311 | MBSetupPageItem *item = [self itemAtIndexPath:indexPath];
312 | NSAssert(item.cellIdentifier != nil, @"cellIdentifier != nil not satisfied");
313 |
314 | MBSetupPageCell *cell = [tableView dequeueReusableCellWithIdentifier:item.cellIdentifier];
315 | if (!cell) {
316 | cell = item.createCellBlock(item);
317 | }
318 |
319 | item.configureCellBlock(item, cell);
320 |
321 | if (item.afterConfigureCellBlock) {
322 | item.afterConfigureCellBlock(item, cell);
323 | }
324 |
325 | CGFloat height = 0;
326 |
327 | if (cell) {
328 | NSAssert(item.cellHeightBlock != nil, @"item.cellHeightBlock != nil not satisfied");
329 | height = item.cellHeightBlock(tableView, item, cell);
330 | }
331 | if (height == 0) {
332 | height = MINIMUM_CELL_HEIGHT;
333 | }
334 |
335 | return height;
336 | }
337 |
338 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
339 | {
340 | MBSetupPageItem *item = [self itemAtIndexPath:indexPath];
341 |
342 | if (item.didSelectBlock) {
343 | item.didSelectBlock(item);
344 | }
345 | }
346 |
347 | #pragma mark - UITextFieldDelegate
348 |
349 | - (void)textFieldDidBeginEditingNotification:(NSNotification *)notification
350 | {
351 | self.activeField = notification.object;
352 | }
353 |
354 | - (void)textFieldDidEndEditingNotification:(NSNotification *)notification
355 | {
356 | self.activeField = nil;
357 | }
358 |
359 | #pragma mark - Keyboard Handling
360 |
361 | - (UIEdgeInsets)tableViewContentInsetByAccountingForKeyboardFrame:(CGRect)keyboardFrame
362 | {
363 | CGRect rect = [self.view convertRect:keyboardFrame fromView:nil];
364 | UIEdgeInsets contentInsets = self.tableView.contentInset;
365 | contentInsets.bottom = rect.size.height;
366 | return contentInsets;
367 | }
368 |
369 | - (void)keyboardDidShowNotification:(NSNotification *)notification
370 | {
371 | CGRect keyboardRect = [(NSValue *)notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
372 |
373 | //Update scroll view inset
374 | UIEdgeInsets contentInset = [self tableViewContentInsetByAccountingForKeyboardFrame:keyboardRect];
375 | self.tableView.contentInset = contentInset;
376 | self.tableView.scrollIndicatorInsets = contentInset;
377 |
378 | //Scroll to active text field
379 | CGRect visibleFrame = self.view.frame;
380 | visibleFrame.size.height -= keyboardRect.size.height;
381 | CGRect activeFieldFrame = [self.view convertRect:self.activeField.frame fromView:self.activeField.superview];
382 |
383 | if (!CGRectContainsPoint(visibleFrame, activeFieldFrame.origin)) {
384 | activeFieldFrame = [self.tableView convertRect:self.activeField.frame fromView:self.activeField.superview];
385 | [self.tableView scrollRectToVisible:activeFieldFrame animated:YES];
386 | }
387 | }
388 |
389 | - (void)keyboardWillHideNotification:(NSNotification *)notification
390 | {
391 | UIEdgeInsets contentInset = [self tableViewContentInsetByAccountingForKeyboardFrame:CGRectZero];
392 | [UIView animateWithDuration:0.3 animations:^{
393 | self.tableView.contentInset = contentInset;
394 | self.tableView.scrollIndicatorInsets = contentInset;
395 | }];
396 | }
397 |
398 | @end
399 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBTextFieldCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBTextFieldCell.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageCell.h"
10 |
11 | @interface MBTextFieldCell : MBSetupPageCell
12 |
13 | @property (nonatomic, readonly, nullable) UILabel *titleLabel;
14 | @property (nonatomic, readonly, nullable) UITextField *textField;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBTextFieldCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBTextFieldCell.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBTextFieldCell.h"
10 | #import "MBTextFieldItem.h"
11 |
12 | static int MBTextFieldCellContext;
13 |
14 | #define DEFAULT_MIN_TITLE_LABEL_WIDTH 110
15 |
16 | @interface MBTextFieldCell()
17 | @property (nonatomic) BOOL updatingItemText;
18 | @end
19 |
20 | @implementation MBTextFieldCell
21 |
22 | - (void)dealloc
23 | {
24 | [self setItem:nil];
25 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:_textField];
26 | }
27 |
28 | #pragma mark - Overridden Methods
29 |
30 | - (void)setItem:(MBTextFieldItem *)item
31 | {
32 | if (self.item != item) {
33 | [self.item removeObserver:self forKeyPath:NSStringFromSelector(@selector(text)) context:&MBTextFieldCellContext];
34 | [super setItem:item];
35 | [self.item addObserver:self forKeyPath:NSStringFromSelector(@selector(text)) options:0 context:&MBTextFieldCellContext];
36 | }
37 | }
38 |
39 | - (void)cellDidLoad
40 | {
41 | [super cellDidLoad];
42 |
43 | UIView *contentView = self.contentView;
44 |
45 | UILabel *titleLabel = [[UILabel alloc] init];
46 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
47 | [titleLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
48 | titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
49 | [contentView addSubview:titleLabel];
50 |
51 | UITextField *textField = [[UITextField alloc] init];
52 | textField.translatesAutoresizingMaskIntoConstraints = NO;
53 | [contentView addSubview:textField];
54 |
55 | NSDictionary *views = NSDictionaryOfVariableBindings(titleLabel, textField);
56 |
57 | [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[titleLabel]-[textField]-|" options:0 metrics:nil views:views]];
58 | [titleLabel addConstraint:[NSLayoutConstraint constraintWithItem:titleLabel
59 | attribute:NSLayoutAttributeWidth
60 | relatedBy:NSLayoutRelationGreaterThanOrEqual
61 | toItem:nil
62 | attribute:0
63 | multiplier:1.0
64 | constant:DEFAULT_MIN_TITLE_LABEL_WIDTH]];
65 |
66 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:contentView
67 | attribute:NSLayoutAttributeCenterY
68 | relatedBy:NSLayoutRelationEqual
69 | toItem:titleLabel
70 | attribute:NSLayoutAttributeCenterY
71 | multiplier:1.0
72 | constant:0.0]];
73 |
74 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:contentView
75 | attribute:NSLayoutAttributeCenterY
76 | relatedBy:NSLayoutRelationEqual
77 | toItem:textField
78 | attribute:NSLayoutAttributeCenterY
79 | multiplier:1.0
80 | constant:0.0]];
81 |
82 | _titleLabel = titleLabel;
83 | _textField = textField;
84 |
85 | textField.delegate = self;
86 |
87 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:UITextFieldTextDidChangeNotification object:textField];
88 | }
89 |
90 | - (void)cellWillAppear
91 | {
92 | [super cellWillAppear];
93 |
94 | MBTextFieldItem *item = (MBTextFieldItem *)self.item;
95 |
96 | self.titleLabel.text = item.title;
97 | self.textField.clearButtonMode = item.clearButtonMode;
98 | self.textField.keyboardType = item.keyboardType;
99 | self.textField.autocorrectionType = item.autocorrectionType;
100 | self.textField.autocapitalizationType = item.autocapitalizationType;
101 | self.textField.secureTextEntry = item.secureTextEntry;
102 | self.textField.text = item.text;
103 | self.textField.placeholder = item.placeholder;
104 | }
105 |
106 | #pragma mark - Overridden Methods
107 |
108 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated
109 | {
110 | [super setSelected:selected animated:animated];
111 |
112 | if (selected) {
113 | //Automatically start editing
114 | [self.textField becomeFirstResponder];
115 | }
116 | }
117 |
118 | #pragma mark - UITextFieldDelegate
119 |
120 | - (void)textFieldDidEndEditing:(UITextField *)textField
121 | {
122 | MBTextFieldItem *item = (MBTextFieldItem *)self.item;
123 | if (item.textDidEndEditingBlock) {
124 | item.textDidEndEditingBlock(item);
125 | }
126 | }
127 |
128 | - (BOOL)textFieldShouldReturn:(UITextField *)textField
129 | {
130 | [textField resignFirstResponder];
131 | return YES;
132 | }
133 |
134 | #pragma mark - Notifications
135 |
136 | - (void)textDidChange:(NSNotification *)notification
137 | {
138 | MBTextFieldItem *item = (MBTextFieldItem *)self.item;
139 |
140 | _updatingItemText = YES;
141 | item.text = self.textField.text;
142 | _updatingItemText = NO;
143 |
144 | if (item.textDidChangeBlock) {
145 | item.textDidChangeBlock(item);
146 | }
147 | }
148 |
149 | #pragma mark - KVO Notifications
150 |
151 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
152 | {
153 | if (context == &MBTextFieldCellContext) {
154 | MBTextFieldItem *item = (MBTextFieldItem *)self.item;
155 |
156 | if ([keyPath isEqualToString:NSStringFromSelector(@selector(text))]) {
157 | //Items text did change
158 | if (!_updatingItemText) {
159 | self.textField.text = item.text;
160 | }
161 | }
162 | } else {
163 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
164 | }
165 | }
166 |
167 | @end
168 |
--------------------------------------------------------------------------------
/SetupController/Classes/MBTextFieldItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MBTextFieldItem.h
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBSetupPageItem.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MBTextFieldItem : MBSetupPageItem
14 |
15 | - (instancetype)initWithTitle:(nullable NSString *)title text:(nullable NSString *)text placeholder:(nullable NSString *)placeholder;
16 |
17 | @property (nonatomic, nullable) NSString *text;
18 | @property (nonatomic, nullable) NSString *placeholder;
19 |
20 | #pragma mark - Customize Text Field Appearance
21 |
22 | @property (nonatomic) UITextFieldViewMode clearButtonMode;
23 | @property (nonatomic) UIKeyboardType keyboardType;
24 | @property (nonatomic) UITextAutocorrectionType autocorrectionType;
25 | @property (nonatomic) UITextAutocapitalizationType autocapitalizationType;
26 | @property (nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;
27 |
28 | #pragma mark - Observe Text Field Changes
29 |
30 | @property (nonatomic, copy, nullable) void(^textDidChangeBlock)(MBTextFieldItem *item);
31 | @property (nonatomic, copy, nullable) void(^textDidEndEditingBlock)(MBTextFieldItem *item);
32 |
33 | @end
34 |
35 | NS_ASSUME_NONNULL_END
--------------------------------------------------------------------------------
/SetupController/Classes/MBTextFieldItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MBTextFieldItem.m
3 | // SetupController
4 | //
5 | // Created by Maksim Bauer on 27/04/14.
6 | // Copyright (c) 2014 Maksim Bauer. All rights reserved.
7 | //
8 |
9 | #import "MBTextFieldItem.h"
10 | #import "MBTextFieldCell.h"
11 |
12 | @implementation MBTextFieldItem
13 |
14 | - (instancetype)initWithTitle:(NSString *)title text:(NSString *)text placeholder:(NSString *)placeholder
15 | {
16 | self = [super initWithTitle:title];
17 | if (self) {
18 | //Make text field cell non-selectable
19 | self.selectionStyle = UITableViewCellSelectionStyleNone;
20 |
21 | _text = text;
22 | _placeholder = placeholder;
23 | _clearButtonMode = UITextFieldViewModeWhileEditing;
24 | _keyboardType = UIKeyboardTypeDefault;
25 | _autocorrectionType = UITextAutocorrectionTypeDefault;
26 | _autocapitalizationType = UITextAutocapitalizationTypeSentences;
27 |
28 | self.createCellBlock = ^MBSetupPageCell *(MBSetupPageItem *item) {
29 | MBTextFieldCell *cell = [[MBTextFieldCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:item.cellIdentifier];
30 | return cell;
31 | };
32 |
33 | self.configureCellBlock = ^(MBSetupPageItem *item, MBSetupPageCell *cell) {
34 | cell.item = item;
35 | };
36 |
37 | self.cellHeightBlock = ^CGFloat(UITableView *tableView, MBSetupPageItem *item, MBSetupPageCell *cell) {
38 | cell.item = item;
39 | CGSize fittingSize = CGSizeMake(tableView.bounds.size.width, 0);
40 | CGSize size = [cell sizeThatFits:fittingSize];
41 | return size.height;
42 | };
43 | }
44 | return self;
45 | }
46 |
47 | - (NSString *)cellIdentifier
48 | {
49 | return @"MBTextFieldCell";
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------