├── .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 | [![CI Status](http://img.shields.io/travis/miximka/SetupController.svg?style=flat)](https://travis-ci.org/miximka/SetupController) 4 | [![Version](https://img.shields.io/cocoapods/v/SetupController.svg?style=flat)](http://cocoadocs.org/docsets/SetupController) 5 | [![License](https://img.shields.io/cocoapods/l/SetupController.svg?style=flat)](http://cocoadocs.org/docsets/SetupController) 6 | [![Platform](https://img.shields.io/cocoapods/p/SetupController.svg?style=flat)](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 | ![SetupController](Images/iPhonePortrait.png) 13 | ![SetupController](Images/iPadLandscape.png) 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 | --------------------------------------------------------------------------------