├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CHANGELOG.md
├── Example
├── Podfile
├── Podfile.lock
├── Pods
│ ├── Local Podspecs
│ │ └── TipSee.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ └── project.pbxproj
│ └── Target Support Files
│ │ ├── Pods-TipSee_Example
│ │ ├── Pods-TipSee_Example-Info.plist
│ │ ├── Pods-TipSee_Example-acknowledgements.markdown
│ │ ├── Pods-TipSee_Example-acknowledgements.plist
│ │ ├── Pods-TipSee_Example-dummy.m
│ │ ├── Pods-TipSee_Example-frameworks.sh
│ │ ├── Pods-TipSee_Example-umbrella.h
│ │ ├── Pods-TipSee_Example.debug.xcconfig
│ │ ├── Pods-TipSee_Example.modulemap
│ │ └── Pods-TipSee_Example.release.xcconfig
│ │ ├── Pods-TipSee_Tests
│ │ ├── Pods-TipSee_Tests-Info.plist
│ │ ├── Pods-TipSee_Tests-acknowledgements.markdown
│ │ ├── Pods-TipSee_Tests-acknowledgements.plist
│ │ ├── Pods-TipSee_Tests-dummy.m
│ │ ├── Pods-TipSee_Tests-umbrella.h
│ │ ├── Pods-TipSee_Tests.debug.xcconfig
│ │ ├── Pods-TipSee_Tests.modulemap
│ │ └── Pods-TipSee_Tests.release.xcconfig
│ │ └── TipSee
│ │ ├── TipSee-Info.plist
│ │ ├── TipSee-dummy.m
│ │ ├── TipSee-prefix.pch
│ │ ├── TipSee-umbrella.h
│ │ ├── TipSee.debug.xcconfig
│ │ ├── TipSee.modulemap
│ │ ├── TipSee.release.xcconfig
│ │ └── TipSee.xcconfig
├── Tests
│ └── Info.plist
├── TipSee.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── TipSee-Example.xcscheme
├── TipSee.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── TipSee
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
│ ├── Images.xcassets
│ ├── 978146509981.imageset
│ │ ├── 978146509981.jpg
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── bottom.imageset
│ │ ├── Contents.json
│ │ └── Screen Shot 2019-10-31 at 11.03.32 PM.png
│ ├── dog.imageset
│ │ ├── Contents.json
│ │ └── Screen Shot 2019-10-31 at 11.00.25 PM.png
│ ├── edit.imageset
│ │ ├── 001-pencil.png
│ │ └── Contents.json
│ ├── eggs.imageset
│ │ ├── Contents.json
│ │ └── Screen Shot 2019-11-01 at 9.59.07 AM.png
│ ├── fashion-profile_2x.imageset
│ │ ├── Contents.json
│ │ └── fashion-profile_2x.png
│ ├── galaxy.imageset
│ │ ├── Contents.json
│ │ └── realistic-galaxy-background_52683-12122.jpg
│ ├── heart-like.imageset
│ │ ├── Contents.json
│ │ └── heart-like.png
│ ├── left-arrow.imageset
│ │ ├── Contents.json
│ │ └── left-arrow.pdf
│ ├── moon.imageset
│ │ ├── Contents.json
│ │ └── moon-icon-flat-01-.jpg
│ ├── photo.imageset
│ │ ├── 002-add.png
│ │ └── Contents.json
│ ├── profile.imageset
│ │ ├── Contents.json
│ │ └── timothy-paul-smith-256424-1200x800.jpg
│ ├── pug_puppy.imageset
│ │ ├── Contents.json
│ │ └── pug_puppy.png
│ ├── right-arrow.imageset
│ │ ├── Contents.json
│ │ └── right-arrow.pdf
│ └── sunny.imageset
│ │ ├── Contents.json
│ │ └── sunny-176-781177.png
│ ├── Info.plist
│ ├── TipSee_Example-Bridging-Header.h
│ ├── ViewController.swift
│ └── images
│ ├── TipSee.gif
│ └── logo.jpg
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── Assets
│ └── .gitkeep
└── TipSee
│ ├── BubbleView.swift
│ ├── NSAttributedString+Size.swift
│ ├── TipItem.swift
│ ├── TipOption.swift
│ ├── TipSee+AttributedText.swift
│ ├── TipSee.swift
│ ├── TipSeeConfiguration.swift
│ ├── TipSeeManager+AttributedText.swift
│ ├── TipSeeManager.swift
│ ├── TipSeeManagerProtocol.swift
│ ├── TipTarget.swift
│ └── UIViewController+Exts.swift
├── Tests
└── TipSeeTests
│ ├── OptionsTests.swift
│ ├── TipItemTests.swift
│ ├── TipSeeManagerTests.swift
│ ├── TipSeeTests.swift
│ └── XCTestManifests.swift
├── TipSee.podspec
└── _Pods.xcodeproj
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
70 | *.DS_Store
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [1.1.0] - 2019-08-06
11 |
12 | ### Added
13 |
14 | - The whole ToolTip-framework. There is no changelog available prior.
15 |
16 | [Unreleased]: https://git.webdooz.com/iOS/tooltip/compare/1.1.0...master
17 | [1.1.0]: https://git.webdooz.com/iOS/tooltip/tags/1.1.0
18 |
19 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | target 'TipSee_Example' do
4 | pod 'TipSee', :path => '../'
5 | platform :ios, '9.0'
6 |
7 | target 'TipSee_Tests' do
8 | inherit! :search_paths
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - TipSee (1.6.0)
3 |
4 | DEPENDENCIES:
5 | - TipSee (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | TipSee:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | TipSee: 6f42ca15824d18615f1ed332b0f823fa370faa2f
13 |
14 | PODFILE CHECKSUM: b049de74e3242ca8cde72714240715a87a8f0d2a
15 |
16 | COCOAPODS: 1.9.1
17 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/TipSee.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TipSee",
3 | "version": "1.6.0",
4 | "module_name": "TipSee",
5 | "summary": "A lightweight, highly customizable tip / hint library for Swift",
6 | "swift_versions": "5.0",
7 | "description": "TipSee is a lightweight and highly customizable library that helps you to show beautiful tips and hints.",
8 | "homepage": "https://github.com/farshadjahanmanesh/TipSee",
9 | "screenshots": [
10 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_1.png",
11 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_2.png",
12 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_3.png",
13 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_4.png",
14 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_5.png",
15 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_6.png",
16 | "https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC.gif"
17 | ],
18 | "license": {
19 | "type": "MIT",
20 | "file": "LICENSE"
21 | },
22 | "authors": {
23 | "farshadjahanmanesh": "farshadjahanmanesh@gmail.com"
24 | },
25 | "source": {
26 | "git": "https://github.com/farshadjahanmanesh/TipSee",
27 | "tag": "1.6.0"
28 | },
29 | "social_media_url": "https://twitter.com/",
30 | "platforms": {
31 | "ios": "9.0"
32 | },
33 | "source_files": "Sources/**/*.swift",
34 | "frameworks": "UIKit",
35 | "swift_version": "5.0"
36 | }
37 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - TipSee (1.6.0)
3 |
4 | DEPENDENCIES:
5 | - TipSee (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | TipSee:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | TipSee: 6f42ca15824d18615f1ed332b0f823fa370faa2f
13 |
14 | PODFILE CHECKSUM: b049de74e3242ca8cde72714240715a87a8f0d2a
15 |
16 | COCOAPODS: 1.9.1
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Example/Pods-TipSee_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-TipSee_Example/Pods-TipSee_Example-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## TipSee
5 |
6 | Copyright (c) 2019 farshadjahanmanesh
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-TipSee_Example/Pods-TipSee_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) 2019 farshadjahanmanesh <farshadjahanmanesh@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 | TipSee
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-TipSee_Example/Pods-TipSee_Example-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_TipSee_Example : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_TipSee_Example
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | function on_error {
7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
8 | }
9 | trap 'on_error $LINENO' ERR
10 |
11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
13 | # frameworks to, so exit 0 (signalling the script phase was successful).
14 | exit 0
15 | fi
16 |
17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
19 |
20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
22 |
23 | # Used as a return value for each invocation of `strip_invalid_archs` function.
24 | STRIP_BINARY_RETVAL=0
25 |
26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
29 |
30 | # Copies and strips a vendored framework
31 | install_framework()
32 | {
33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
34 | local source="${BUILT_PRODUCTS_DIR}/$1"
35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
37 | elif [ -r "$1" ]; then
38 | local source="$1"
39 | fi
40 |
41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
42 |
43 | if [ -L "${source}" ]; then
44 | echo "Symlinked..."
45 | source="$(readlink "${source}")"
46 | fi
47 |
48 | # Use filter instead of exclude so missing patterns don't throw errors.
49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
51 |
52 | local basename
53 | basename="$(basename -s .framework "$1")"
54 | binary="${destination}/${basename}.framework/${basename}"
55 |
56 | if ! [ -r "$binary" ]; then
57 | binary="${destination}/${basename}"
58 | elif [ -L "${binary}" ]; then
59 | echo "Destination binary is symlinked..."
60 | dirname="$(dirname "${binary}")"
61 | binary="${dirname}/$(readlink "${binary}")"
62 | fi
63 |
64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
66 | strip_invalid_archs "$binary"
67 | fi
68 |
69 | # Resign the code if required by the build settings to avoid unstable apps
70 | code_sign_if_enabled "${destination}/$(basename "$1")"
71 |
72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
74 | local swift_runtime_libs
75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
76 | for lib in $swift_runtime_libs; do
77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
79 | code_sign_if_enabled "${destination}/${lib}"
80 | done
81 | fi
82 | }
83 |
84 | # Copies and strips a vendored dSYM
85 | install_dsym() {
86 | local source="$1"
87 | warn_missing_arch=${2:-true}
88 | if [ -r "$source" ]; then
89 | # Copy the dSYM into the targets temp dir.
90 | 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}\""
91 | 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}"
92 |
93 | local basename
94 | basename="$(basename -s .dSYM "$source")"
95 | binary_name="$(ls "$source/Contents/Resources/DWARF")"
96 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}"
97 |
98 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
99 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then
100 | strip_invalid_archs "$binary" "$warn_missing_arch"
101 | fi
102 |
103 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
104 | # Move the stripped file into its final destination.
105 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
106 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
107 | else
108 | # 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.
109 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM"
110 | fi
111 | fi
112 | }
113 |
114 | # Copies the bcsymbolmap files of a vendored framework
115 | install_bcsymbolmap() {
116 | local bcsymbolmap_path="$1"
117 | local destination="${BUILT_PRODUCTS_DIR}"
118 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}""
119 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"
120 | }
121 |
122 | # Signs a framework with the provided identity
123 | code_sign_if_enabled() {
124 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
125 | # Use the current code_sign_identity
126 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
127 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
128 |
129 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
130 | code_sign_cmd="$code_sign_cmd &"
131 | fi
132 | echo "$code_sign_cmd"
133 | eval "$code_sign_cmd"
134 | fi
135 | }
136 |
137 | # Strip invalid architectures
138 | strip_invalid_archs() {
139 | binary="$1"
140 | warn_missing_arch=${2:-true}
141 | # Get architectures for current target binary
142 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
143 | # Intersect them with the architectures we are building for
144 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
145 | # If there are no archs supported by this binary then warn the user
146 | if [[ -z "$intersected_archs" ]]; then
147 | if [[ "$warn_missing_arch" == "true" ]]; then
148 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
149 | fi
150 | STRIP_BINARY_RETVAL=0
151 | return
152 | fi
153 | stripped=""
154 | for arch in $binary_archs; do
155 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
156 | # Strip non-valid architectures in-place
157 | lipo -remove "$arch" -output "$binary" "$binary"
158 | stripped="$stripped $arch"
159 | fi
160 | done
161 | if [[ "$stripped" ]]; then
162 | echo "Stripped $binary of architectures:$stripped"
163 | fi
164 | STRIP_BINARY_RETVAL=1
165 | }
166 |
167 | install_artifact() {
168 | artifact="$1"
169 | base="$(basename "$artifact")"
170 | case $base in
171 | *.framework)
172 | install_framework "$artifact"
173 | ;;
174 | *.dSYM)
175 | # Suppress arch warnings since XCFrameworks will include many dSYM files
176 | install_dsym "$artifact" "false"
177 | ;;
178 | *.bcsymbolmap)
179 | install_bcsymbolmap "$artifact"
180 | ;;
181 | *)
182 | echo "error: Unrecognized artifact "$artifact""
183 | ;;
184 | esac
185 | }
186 |
187 | copy_artifacts() {
188 | file_list="$1"
189 | while read artifact; do
190 | install_artifact "$artifact"
191 | done <$file_list
192 | }
193 |
194 | ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt"
195 | if [ -r "${ARTIFACT_LIST_FILE}" ]; then
196 | copy_artifacts "${ARTIFACT_LIST_FILE}"
197 | fi
198 |
199 | if [[ "$CONFIGURATION" == "Debug" ]]; then
200 | install_framework "${BUILT_PRODUCTS_DIR}/TipSee/TipSee.framework"
201 | fi
202 | if [[ "$CONFIGURATION" == "Release" ]]; then
203 | install_framework "${BUILT_PRODUCTS_DIR}/TipSee/TipSee.framework"
204 | fi
205 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
206 | wait
207 | fi
208 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Example/Pods-TipSee_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_TipSee_ExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_TipSee_ExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee/TipSee.framework/Headers"
5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
6 | OTHER_LDFLAGS = $(inherited) -framework "TipSee" -framework "UIKit"
7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_TipSee_Example {
2 | umbrella header "Pods-TipSee_Example-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee/TipSee.framework/Headers"
5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
6 | OTHER_LDFLAGS = $(inherited) -framework "TipSee" -framework "UIKit"
7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Tests/Pods-TipSee_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-TipSee_Tests/Pods-TipSee_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-TipSee_Tests/Pods-TipSee_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-TipSee_Tests/Pods-TipSee_Tests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_TipSee_Tests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_TipSee_Tests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Tests/Pods-TipSee_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_TipSee_TestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_TipSee_TestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Tests/Pods-TipSee_Tests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee/TipSee.framework/Headers"
4 | OTHER_LDFLAGS = $(inherited) -framework "TipSee" -framework "UIKit"
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 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
10 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Tests/Pods-TipSee_Tests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_TipSee_Tests {
2 | umbrella header "Pods-TipSee_Tests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-TipSee_Tests/Pods-TipSee_Tests.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/TipSee/TipSee.framework/Headers"
4 | OTHER_LDFLAGS = $(inherited) -framework "TipSee" -framework "UIKit"
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 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
10 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee-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.6.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_TipSee : NSObject
3 | @end
4 | @implementation PodsDummy_TipSee
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee-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/TipSee/TipSee-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 TipSeeVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char TipSeeVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TipSee
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_LDFLAGS = $(inherited) -framework "UIKit"
4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee.modulemap:
--------------------------------------------------------------------------------
1 | framework module TipSee {
2 | umbrella header "TipSee-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee.release.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TipSee
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_LDFLAGS = $(inherited) -framework "UIKit"
4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/TipSee/TipSee.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TipSee
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
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/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/TipSee.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 53AF54132438B78E00B26705 /* OptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AF54092438B78000B26705 /* OptionsTests.swift */; };
11 | 53AF54142438B78E00B26705 /* TipSeeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AF540A2438B78000B26705 /* TipSeeTests.swift */; };
12 | 53AF54152438B78E00B26705 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AF540B2438B78000B26705 /* XCTestManifests.swift */; };
13 | 53AF54162438B78E00B26705 /* TipItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AF540C2438B78000B26705 /* TipItemTests.swift */; };
14 | 53AF54172438B78E00B26705 /* TipSeeManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AF540D2438B78000B26705 /* TipSeeManagerTests.swift */; };
15 | 5AC6E7D42FDCDF1B8F12B48B /* Pods_TipSee_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E79AABA03E0B1320CB887CE /* Pods_TipSee_Tests.framework */; };
16 | 87BCBA3D2358E166000BCF46 /* TipSee.gif in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA352358E166000BCF46 /* TipSee.gif */; };
17 | 87BCBA442358E166000BCF46 /* logo.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA3C2358E166000BCF46 /* logo.jpg */; };
18 | 87BCBA462358E202000BCF46 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA452358E202000BCF46 /* Images.xcassets */; };
19 | 87BCBA492358E212000BCF46 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BCBA482358E211000BCF46 /* AppDelegate.swift */; };
20 | 87BCBA502358E21D000BCF46 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BCBA4A2358E21D000BCF46 /* ViewController.swift */; };
21 | 87BCBA512358E21D000BCF46 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA4C2358E21D000BCF46 /* LaunchScreen.xib */; };
22 | 87BCBA522358E21D000BCF46 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA4E2358E21D000BCF46 /* Main.storyboard */; };
23 | 87BCBA582358E3FB000BCF46 /* TipSee.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA532358E3FB000BCF46 /* TipSee.podspec */; };
24 | 87BCBA592358E3FB000BCF46 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA542358E3FB000BCF46 /* LICENSE */; };
25 | 87BCBA5A2358E3FB000BCF46 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA552358E3FB000BCF46 /* CHANGELOG.md */; };
26 | 87BCBA5C2358E3FB000BCF46 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 87BCBA572358E3FB000BCF46 /* README.md */; };
27 | A5DA80CC545C67E4D77AE5EF /* Pods_TipSee_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBADEF3899A7E55B834BF071 /* Pods_TipSee_Example.framework */; };
28 | /* End PBXBuildFile section */
29 |
30 | /* Begin PBXContainerItemProxy section */
31 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
32 | isa = PBXContainerItemProxy;
33 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
34 | proxyType = 1;
35 | remoteGlobalIDString = 607FACCF1AFB9204008FA782;
36 | remoteInfo = TipSee;
37 | };
38 | /* End PBXContainerItemProxy section */
39 |
40 | /* Begin PBXFileReference section */
41 | 0E79AABA03E0B1320CB887CE /* Pods_TipSee_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TipSee_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
42 | 185AE1F4B18EDFA276B12D07 /* Pods-TipSee_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TipSee_Example.debug.xcconfig"; path = "Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example.debug.xcconfig"; sourceTree = ""; };
43 | 1E84A910905483281EED216C /* Pods-TipSee_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TipSee_Tests.debug.xcconfig"; path = "Target Support Files/Pods-TipSee_Tests/Pods-TipSee_Tests.debug.xcconfig"; sourceTree = ""; };
44 | 53AF54092438B78000B26705 /* OptionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsTests.swift; sourceTree = ""; };
45 | 53AF540A2438B78000B26705 /* TipSeeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TipSeeTests.swift; sourceTree = ""; };
46 | 53AF540B2438B78000B26705 /* XCTestManifests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; };
47 | 53AF540C2438B78000B26705 /* TipItemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TipItemTests.swift; sourceTree = ""; };
48 | 53AF540D2438B78000B26705 /* TipSeeManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TipSeeManagerTests.swift; sourceTree = ""; };
49 | 607FACD01AFB9204008FA782 /* TipSee_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TipSee_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | 607FACE51AFB9204008FA782 /* TipSee_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TipSee_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
52 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | 6E4A28C99435F4EA34213DE2 /* Pods-TipSee_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TipSee_Tests.release.xcconfig"; path = "Target Support Files/Pods-TipSee_Tests/Pods-TipSee_Tests.release.xcconfig"; sourceTree = ""; };
54 | 87BCBA352358E166000BCF46 /* TipSee.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = TipSee.gif; sourceTree = ""; };
55 | 87BCBA3C2358E166000BCF46 /* logo.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = logo.jpg; sourceTree = ""; };
56 | 87BCBA452358E202000BCF46 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
57 | 87BCBA472358E211000BCF46 /* TipSee_Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TipSee_Example-Bridging-Header.h"; sourceTree = ""; };
58 | 87BCBA482358E211000BCF46 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
59 | 87BCBA4A2358E21D000BCF46 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
60 | 87BCBA4D2358E21D000BCF46 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = LaunchScreen.xib; sourceTree = ""; };
61 | 87BCBA4F2358E21D000BCF46 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = ""; };
62 | 87BCBA532358E3FB000BCF46 /* TipSee.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = TipSee.podspec; path = ../TipSee.podspec; sourceTree = ""; };
63 | 87BCBA542358E3FB000BCF46 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
64 | 87BCBA552358E3FB000BCF46 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; };
65 | 87BCBA572358E3FB000BCF46 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
66 | A41F56FC353C7EC3C9F6E995 /* Pods-TipSee_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TipSee_Example.release.xcconfig"; path = "Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example.release.xcconfig"; sourceTree = ""; };
67 | BBADEF3899A7E55B834BF071 /* Pods_TipSee_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TipSee_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
68 | /* End PBXFileReference section */
69 |
70 | /* Begin PBXFrameworksBuildPhase section */
71 | 607FACCD1AFB9204008FA782 /* Frameworks */ = {
72 | isa = PBXFrameworksBuildPhase;
73 | buildActionMask = 2147483647;
74 | files = (
75 | A5DA80CC545C67E4D77AE5EF /* Pods_TipSee_Example.framework in Frameworks */,
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | 607FACE21AFB9204008FA782 /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | 5AC6E7D42FDCDF1B8F12B48B /* Pods_TipSee_Tests.framework in Frameworks */,
84 | );
85 | runOnlyForDeploymentPostprocessing = 0;
86 | };
87 | /* End PBXFrameworksBuildPhase section */
88 |
89 | /* Begin PBXGroup section */
90 | 53AF54082438B78000B26705 /* TipSeeTests */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 53AF54092438B78000B26705 /* OptionsTests.swift */,
94 | 53AF540A2438B78000B26705 /* TipSeeTests.swift */,
95 | 53AF540B2438B78000B26705 /* XCTestManifests.swift */,
96 | 53AF540C2438B78000B26705 /* TipItemTests.swift */,
97 | 53AF540D2438B78000B26705 /* TipSeeManagerTests.swift */,
98 | );
99 | name = TipSeeTests;
100 | path = ../../Tests/TipSeeTests;
101 | sourceTree = "";
102 | };
103 | 607FACC71AFB9204008FA782 = {
104 | isa = PBXGroup;
105 | children = (
106 | 607FACF51AFB993E008FA782 /* Podspec Metadata */,
107 | 607FACD21AFB9204008FA782 /* Example for TipSee */,
108 | 607FACE81AFB9204008FA782 /* Tests */,
109 | 607FACD11AFB9204008FA782 /* Products */,
110 | BA030862CC1C4DE09AAB79A1 /* Pods */,
111 | C906ACBFC6B38C291DC1BEF7 /* Frameworks */,
112 | );
113 | sourceTree = "";
114 | };
115 | 607FACD11AFB9204008FA782 /* Products */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 607FACD01AFB9204008FA782 /* TipSee_Example.app */,
119 | 607FACE51AFB9204008FA782 /* TipSee_Tests.xctest */,
120 | );
121 | name = Products;
122 | sourceTree = "";
123 | };
124 | 607FACD21AFB9204008FA782 /* Example for TipSee */ = {
125 | isa = PBXGroup;
126 | children = (
127 | 87BCBA482358E211000BCF46 /* AppDelegate.swift */,
128 | 87BCBA4B2358E21D000BCF46 /* Base.lproj */,
129 | 87BCBA4A2358E21D000BCF46 /* ViewController.swift */,
130 | 87BCBA452358E202000BCF46 /* Images.xcassets */,
131 | 87BCBA342358E166000BCF46 /* images */,
132 | 607FACD31AFB9204008FA782 /* Supporting Files */,
133 | 87BCBA472358E211000BCF46 /* TipSee_Example-Bridging-Header.h */,
134 | );
135 | name = "Example for TipSee";
136 | path = TipSee;
137 | sourceTree = "";
138 | };
139 | 607FACD31AFB9204008FA782 /* Supporting Files */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 607FACD41AFB9204008FA782 /* Info.plist */,
143 | );
144 | name = "Supporting Files";
145 | sourceTree = "";
146 | };
147 | 607FACE81AFB9204008FA782 /* Tests */ = {
148 | isa = PBXGroup;
149 | children = (
150 | 53AF54082438B78000B26705 /* TipSeeTests */,
151 | 607FACE91AFB9204008FA782 /* Supporting Files */,
152 | );
153 | path = Tests;
154 | sourceTree = "";
155 | };
156 | 607FACE91AFB9204008FA782 /* Supporting Files */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 607FACEA1AFB9204008FA782 /* Info.plist */,
160 | );
161 | name = "Supporting Files";
162 | sourceTree = "";
163 | };
164 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
165 | isa = PBXGroup;
166 | children = (
167 | 87BCBA552358E3FB000BCF46 /* CHANGELOG.md */,
168 | 87BCBA542358E3FB000BCF46 /* LICENSE */,
169 | 87BCBA572358E3FB000BCF46 /* README.md */,
170 | 87BCBA532358E3FB000BCF46 /* TipSee.podspec */,
171 | );
172 | name = "Podspec Metadata";
173 | sourceTree = "";
174 | };
175 | 87BCBA342358E166000BCF46 /* images */ = {
176 | isa = PBXGroup;
177 | children = (
178 | 87BCBA352358E166000BCF46 /* TipSee.gif */,
179 | 87BCBA3C2358E166000BCF46 /* logo.jpg */,
180 | );
181 | path = images;
182 | sourceTree = "";
183 | };
184 | 87BCBA4B2358E21D000BCF46 /* Base.lproj */ = {
185 | isa = PBXGroup;
186 | children = (
187 | 87BCBA4C2358E21D000BCF46 /* LaunchScreen.xib */,
188 | 87BCBA4E2358E21D000BCF46 /* Main.storyboard */,
189 | );
190 | path = Base.lproj;
191 | sourceTree = "";
192 | };
193 | BA030862CC1C4DE09AAB79A1 /* Pods */ = {
194 | isa = PBXGroup;
195 | children = (
196 | 185AE1F4B18EDFA276B12D07 /* Pods-TipSee_Example.debug.xcconfig */,
197 | A41F56FC353C7EC3C9F6E995 /* Pods-TipSee_Example.release.xcconfig */,
198 | 1E84A910905483281EED216C /* Pods-TipSee_Tests.debug.xcconfig */,
199 | 6E4A28C99435F4EA34213DE2 /* Pods-TipSee_Tests.release.xcconfig */,
200 | );
201 | path = Pods;
202 | sourceTree = "";
203 | };
204 | C906ACBFC6B38C291DC1BEF7 /* Frameworks */ = {
205 | isa = PBXGroup;
206 | children = (
207 | BBADEF3899A7E55B834BF071 /* Pods_TipSee_Example.framework */,
208 | 0E79AABA03E0B1320CB887CE /* Pods_TipSee_Tests.framework */,
209 | );
210 | name = Frameworks;
211 | sourceTree = "";
212 | };
213 | /* End PBXGroup section */
214 |
215 | /* Begin PBXNativeTarget section */
216 | 607FACCF1AFB9204008FA782 /* TipSee_Example */ = {
217 | isa = PBXNativeTarget;
218 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "TipSee_Example" */;
219 | buildPhases = (
220 | DD5D0DA56B7CF940AD85E7B2 /* [CP] Check Pods Manifest.lock */,
221 | 607FACCC1AFB9204008FA782 /* Sources */,
222 | 607FACCD1AFB9204008FA782 /* Frameworks */,
223 | 607FACCE1AFB9204008FA782 /* Resources */,
224 | 44F0E925B2C39F9A7BEC72F9 /* [CP] Embed Pods Frameworks */,
225 | );
226 | buildRules = (
227 | );
228 | dependencies = (
229 | );
230 | name = TipSee_Example;
231 | productName = TipSee;
232 | productReference = 607FACD01AFB9204008FA782 /* TipSee_Example.app */;
233 | productType = "com.apple.product-type.application";
234 | };
235 | 607FACE41AFB9204008FA782 /* TipSee_Tests */ = {
236 | isa = PBXNativeTarget;
237 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "TipSee_Tests" */;
238 | buildPhases = (
239 | 69217D45001E578D2FADAED2 /* [CP] Check Pods Manifest.lock */,
240 | 607FACE11AFB9204008FA782 /* Sources */,
241 | 607FACE21AFB9204008FA782 /* Frameworks */,
242 | 607FACE31AFB9204008FA782 /* Resources */,
243 | );
244 | buildRules = (
245 | );
246 | dependencies = (
247 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */,
248 | );
249 | name = TipSee_Tests;
250 | productName = Tests;
251 | productReference = 607FACE51AFB9204008FA782 /* TipSee_Tests.xctest */;
252 | productType = "com.apple.product-type.bundle.unit-test";
253 | };
254 | /* End PBXNativeTarget section */
255 |
256 | /* Begin PBXProject section */
257 | 607FACC81AFB9204008FA782 /* Project object */ = {
258 | isa = PBXProject;
259 | attributes = {
260 | LastSwiftUpdateCheck = 1110;
261 | LastUpgradeCheck = 1110;
262 | ORGANIZATIONNAME = CocoaPods;
263 | TargetAttributes = {
264 | 607FACCF1AFB9204008FA782 = {
265 | CreatedOnToolsVersion = 6.3.1;
266 | LastSwiftMigration = 1110;
267 | };
268 | 607FACE41AFB9204008FA782 = {
269 | CreatedOnToolsVersion = 6.3.1;
270 | LastSwiftMigration = 1110;
271 | TestTargetID = 607FACCF1AFB9204008FA782;
272 | };
273 | };
274 | };
275 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "TipSee" */;
276 | compatibilityVersion = "Xcode 3.2";
277 | developmentRegion = en;
278 | hasScannedForEncodings = 0;
279 | knownRegions = (
280 | en,
281 | Base,
282 | );
283 | mainGroup = 607FACC71AFB9204008FA782;
284 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
285 | projectDirPath = "";
286 | projectRoot = "";
287 | targets = (
288 | 607FACCF1AFB9204008FA782 /* TipSee_Example */,
289 | 607FACE41AFB9204008FA782 /* TipSee_Tests */,
290 | );
291 | };
292 | /* End PBXProject section */
293 |
294 | /* Begin PBXResourcesBuildPhase section */
295 | 607FACCE1AFB9204008FA782 /* Resources */ = {
296 | isa = PBXResourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | 87BCBA512358E21D000BCF46 /* LaunchScreen.xib in Resources */,
300 | 87BCBA462358E202000BCF46 /* Images.xcassets in Resources */,
301 | 87BCBA592358E3FB000BCF46 /* LICENSE in Resources */,
302 | 87BCBA5A2358E3FB000BCF46 /* CHANGELOG.md in Resources */,
303 | 87BCBA5C2358E3FB000BCF46 /* README.md in Resources */,
304 | 87BCBA442358E166000BCF46 /* logo.jpg in Resources */,
305 | 87BCBA522358E21D000BCF46 /* Main.storyboard in Resources */,
306 | 87BCBA3D2358E166000BCF46 /* TipSee.gif in Resources */,
307 | 87BCBA582358E3FB000BCF46 /* TipSee.podspec in Resources */,
308 | );
309 | runOnlyForDeploymentPostprocessing = 0;
310 | };
311 | 607FACE31AFB9204008FA782 /* Resources */ = {
312 | isa = PBXResourcesBuildPhase;
313 | buildActionMask = 2147483647;
314 | files = (
315 | );
316 | runOnlyForDeploymentPostprocessing = 0;
317 | };
318 | /* End PBXResourcesBuildPhase section */
319 |
320 | /* Begin PBXShellScriptBuildPhase section */
321 | 44F0E925B2C39F9A7BEC72F9 /* [CP] Embed Pods Frameworks */ = {
322 | isa = PBXShellScriptBuildPhase;
323 | buildActionMask = 2147483647;
324 | files = (
325 | );
326 | inputPaths = (
327 | "${PODS_ROOT}/Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example-frameworks.sh",
328 | "${BUILT_PRODUCTS_DIR}/TipSee/TipSee.framework",
329 | );
330 | name = "[CP] Embed Pods Frameworks";
331 | outputPaths = (
332 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TipSee.framework",
333 | );
334 | runOnlyForDeploymentPostprocessing = 0;
335 | shellPath = /bin/sh;
336 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TipSee_Example/Pods-TipSee_Example-frameworks.sh\"\n";
337 | showEnvVarsInLog = 0;
338 | };
339 | 69217D45001E578D2FADAED2 /* [CP] Check Pods Manifest.lock */ = {
340 | isa = PBXShellScriptBuildPhase;
341 | buildActionMask = 2147483647;
342 | files = (
343 | );
344 | inputFileListPaths = (
345 | );
346 | inputPaths = (
347 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
348 | "${PODS_ROOT}/Manifest.lock",
349 | );
350 | name = "[CP] Check Pods Manifest.lock";
351 | outputFileListPaths = (
352 | );
353 | outputPaths = (
354 | "$(DERIVED_FILE_DIR)/Pods-TipSee_Tests-checkManifestLockResult.txt",
355 | );
356 | runOnlyForDeploymentPostprocessing = 0;
357 | shellPath = /bin/sh;
358 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
359 | showEnvVarsInLog = 0;
360 | };
361 | DD5D0DA56B7CF940AD85E7B2 /* [CP] Check Pods Manifest.lock */ = {
362 | isa = PBXShellScriptBuildPhase;
363 | buildActionMask = 2147483647;
364 | files = (
365 | );
366 | inputFileListPaths = (
367 | );
368 | inputPaths = (
369 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
370 | "${PODS_ROOT}/Manifest.lock",
371 | );
372 | name = "[CP] Check Pods Manifest.lock";
373 | outputFileListPaths = (
374 | );
375 | outputPaths = (
376 | "$(DERIVED_FILE_DIR)/Pods-TipSee_Example-checkManifestLockResult.txt",
377 | );
378 | runOnlyForDeploymentPostprocessing = 0;
379 | shellPath = /bin/sh;
380 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
381 | showEnvVarsInLog = 0;
382 | };
383 | /* End PBXShellScriptBuildPhase section */
384 |
385 | /* Begin PBXSourcesBuildPhase section */
386 | 607FACCC1AFB9204008FA782 /* Sources */ = {
387 | isa = PBXSourcesBuildPhase;
388 | buildActionMask = 2147483647;
389 | files = (
390 | 87BCBA502358E21D000BCF46 /* ViewController.swift in Sources */,
391 | 87BCBA492358E212000BCF46 /* AppDelegate.swift in Sources */,
392 | );
393 | runOnlyForDeploymentPostprocessing = 0;
394 | };
395 | 607FACE11AFB9204008FA782 /* Sources */ = {
396 | isa = PBXSourcesBuildPhase;
397 | buildActionMask = 2147483647;
398 | files = (
399 | 53AF54152438B78E00B26705 /* XCTestManifests.swift in Sources */,
400 | 53AF54142438B78E00B26705 /* TipSeeTests.swift in Sources */,
401 | 53AF54172438B78E00B26705 /* TipSeeManagerTests.swift in Sources */,
402 | 53AF54162438B78E00B26705 /* TipItemTests.swift in Sources */,
403 | 53AF54132438B78E00B26705 /* OptionsTests.swift in Sources */,
404 | );
405 | runOnlyForDeploymentPostprocessing = 0;
406 | };
407 | /* End PBXSourcesBuildPhase section */
408 |
409 | /* Begin PBXTargetDependency section */
410 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
411 | isa = PBXTargetDependency;
412 | target = 607FACCF1AFB9204008FA782 /* TipSee_Example */;
413 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
414 | };
415 | /* End PBXTargetDependency section */
416 |
417 | /* Begin PBXVariantGroup section */
418 | 87BCBA4C2358E21D000BCF46 /* LaunchScreen.xib */ = {
419 | isa = PBXVariantGroup;
420 | children = (
421 | 87BCBA4D2358E21D000BCF46 /* Base */,
422 | );
423 | name = LaunchScreen.xib;
424 | sourceTree = "";
425 | };
426 | 87BCBA4E2358E21D000BCF46 /* Main.storyboard */ = {
427 | isa = PBXVariantGroup;
428 | children = (
429 | 87BCBA4F2358E21D000BCF46 /* Base */,
430 | );
431 | name = Main.storyboard;
432 | sourceTree = "";
433 | };
434 | /* End PBXVariantGroup section */
435 |
436 | /* Begin XCBuildConfiguration section */
437 | 607FACED1AFB9204008FA782 /* Debug */ = {
438 | isa = XCBuildConfiguration;
439 | buildSettings = {
440 | ALWAYS_SEARCH_USER_PATHS = NO;
441 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
442 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
443 | CLANG_CXX_LIBRARY = "libc++";
444 | CLANG_ENABLE_MODULES = YES;
445 | CLANG_ENABLE_OBJC_ARC = YES;
446 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
447 | CLANG_WARN_BOOL_CONVERSION = YES;
448 | CLANG_WARN_COMMA = YES;
449 | CLANG_WARN_CONSTANT_CONVERSION = YES;
450 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
452 | CLANG_WARN_EMPTY_BODY = YES;
453 | CLANG_WARN_ENUM_CONVERSION = YES;
454 | CLANG_WARN_INFINITE_RECURSION = YES;
455 | CLANG_WARN_INT_CONVERSION = YES;
456 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
457 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
458 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
459 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
460 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
461 | CLANG_WARN_STRICT_PROTOTYPES = YES;
462 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
463 | CLANG_WARN_UNREACHABLE_CODE = YES;
464 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
465 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
466 | COPY_PHASE_STRIP = NO;
467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
468 | ENABLE_STRICT_OBJC_MSGSEND = YES;
469 | ENABLE_TESTABILITY = YES;
470 | GCC_C_LANGUAGE_STANDARD = gnu99;
471 | GCC_DYNAMIC_NO_PIC = NO;
472 | GCC_NO_COMMON_BLOCKS = YES;
473 | GCC_OPTIMIZATION_LEVEL = 0;
474 | GCC_PREPROCESSOR_DEFINITIONS = (
475 | "DEBUG=1",
476 | "$(inherited)",
477 | );
478 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
479 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
480 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
481 | GCC_WARN_UNDECLARED_SELECTOR = YES;
482 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
483 | GCC_WARN_UNUSED_FUNCTION = YES;
484 | GCC_WARN_UNUSED_VARIABLE = YES;
485 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
486 | MTL_ENABLE_DEBUG_INFO = YES;
487 | ONLY_ACTIVE_ARCH = YES;
488 | SDKROOT = iphoneos;
489 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
490 | };
491 | name = Debug;
492 | };
493 | 607FACEE1AFB9204008FA782 /* Release */ = {
494 | isa = XCBuildConfiguration;
495 | buildSettings = {
496 | ALWAYS_SEARCH_USER_PATHS = NO;
497 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
498 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
499 | CLANG_CXX_LIBRARY = "libc++";
500 | CLANG_ENABLE_MODULES = YES;
501 | CLANG_ENABLE_OBJC_ARC = YES;
502 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
503 | CLANG_WARN_BOOL_CONVERSION = YES;
504 | CLANG_WARN_COMMA = YES;
505 | CLANG_WARN_CONSTANT_CONVERSION = YES;
506 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
507 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
508 | CLANG_WARN_EMPTY_BODY = YES;
509 | CLANG_WARN_ENUM_CONVERSION = YES;
510 | CLANG_WARN_INFINITE_RECURSION = YES;
511 | CLANG_WARN_INT_CONVERSION = YES;
512 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
513 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
514 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
515 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
516 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
517 | CLANG_WARN_STRICT_PROTOTYPES = YES;
518 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
519 | CLANG_WARN_UNREACHABLE_CODE = YES;
520 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
521 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
522 | COPY_PHASE_STRIP = NO;
523 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
524 | ENABLE_NS_ASSERTIONS = NO;
525 | ENABLE_STRICT_OBJC_MSGSEND = YES;
526 | GCC_C_LANGUAGE_STANDARD = gnu99;
527 | GCC_NO_COMMON_BLOCKS = YES;
528 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
529 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
530 | GCC_WARN_UNDECLARED_SELECTOR = YES;
531 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
532 | GCC_WARN_UNUSED_FUNCTION = YES;
533 | GCC_WARN_UNUSED_VARIABLE = YES;
534 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
535 | MTL_ENABLE_DEBUG_INFO = NO;
536 | SDKROOT = iphoneos;
537 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
538 | VALIDATE_PRODUCT = YES;
539 | };
540 | name = Release;
541 | };
542 | 607FACF01AFB9204008FA782 /* Debug */ = {
543 | isa = XCBuildConfiguration;
544 | baseConfigurationReference = 185AE1F4B18EDFA276B12D07 /* Pods-TipSee_Example.debug.xcconfig */;
545 | buildSettings = {
546 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
547 | CLANG_ENABLE_MODULES = YES;
548 | INFOPLIST_FILE = TipSee/Info.plist;
549 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
550 | MODULE_NAME = ExampleApp;
551 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
552 | PRODUCT_NAME = "$(TARGET_NAME)";
553 | SWIFT_OBJC_BRIDGING_HEADER = "TipSee/TipSee_Example-Bridging-Header.h";
554 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
555 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
556 | SWIFT_VERSION = 5.0;
557 | };
558 | name = Debug;
559 | };
560 | 607FACF11AFB9204008FA782 /* Release */ = {
561 | isa = XCBuildConfiguration;
562 | baseConfigurationReference = A41F56FC353C7EC3C9F6E995 /* Pods-TipSee_Example.release.xcconfig */;
563 | buildSettings = {
564 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
565 | CLANG_ENABLE_MODULES = YES;
566 | INFOPLIST_FILE = TipSee/Info.plist;
567 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
568 | MODULE_NAME = ExampleApp;
569 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
570 | PRODUCT_NAME = "$(TARGET_NAME)";
571 | SWIFT_OBJC_BRIDGING_HEADER = "TipSee/TipSee_Example-Bridging-Header.h";
572 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
573 | SWIFT_VERSION = 5.0;
574 | };
575 | name = Release;
576 | };
577 | 607FACF31AFB9204008FA782 /* Debug */ = {
578 | isa = XCBuildConfiguration;
579 | baseConfigurationReference = 1E84A910905483281EED216C /* Pods-TipSee_Tests.debug.xcconfig */;
580 | buildSettings = {
581 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
582 | FRAMEWORK_SEARCH_PATHS = (
583 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
584 | "$(inherited)",
585 | );
586 | GCC_PREPROCESSOR_DEFINITIONS = (
587 | "DEBUG=1",
588 | "$(inherited)",
589 | );
590 | INFOPLIST_FILE = Tests/Info.plist;
591 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
592 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
593 | PRODUCT_NAME = "$(TARGET_NAME)";
594 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
595 | SWIFT_VERSION = 5.0;
596 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TipSee_Example.app/TipSee_Example";
597 | };
598 | name = Debug;
599 | };
600 | 607FACF41AFB9204008FA782 /* Release */ = {
601 | isa = XCBuildConfiguration;
602 | baseConfigurationReference = 6E4A28C99435F4EA34213DE2 /* Pods-TipSee_Tests.release.xcconfig */;
603 | buildSettings = {
604 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
605 | FRAMEWORK_SEARCH_PATHS = (
606 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
607 | "$(inherited)",
608 | );
609 | INFOPLIST_FILE = Tests/Info.plist;
610 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
611 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
612 | PRODUCT_NAME = "$(TARGET_NAME)";
613 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
614 | SWIFT_VERSION = 5.0;
615 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TipSee_Example.app/TipSee_Example";
616 | };
617 | name = Release;
618 | };
619 | /* End XCBuildConfiguration section */
620 |
621 | /* Begin XCConfigurationList section */
622 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "TipSee" */ = {
623 | isa = XCConfigurationList;
624 | buildConfigurations = (
625 | 607FACED1AFB9204008FA782 /* Debug */,
626 | 607FACEE1AFB9204008FA782 /* Release */,
627 | );
628 | defaultConfigurationIsVisible = 0;
629 | defaultConfigurationName = Release;
630 | };
631 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "TipSee_Example" */ = {
632 | isa = XCConfigurationList;
633 | buildConfigurations = (
634 | 607FACF01AFB9204008FA782 /* Debug */,
635 | 607FACF11AFB9204008FA782 /* Release */,
636 | );
637 | defaultConfigurationIsVisible = 0;
638 | defaultConfigurationName = Release;
639 | };
640 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "TipSee_Tests" */ = {
641 | isa = XCConfigurationList;
642 | buildConfigurations = (
643 | 607FACF31AFB9204008FA782 /* Debug */,
644 | 607FACF41AFB9204008FA782 /* Release */,
645 | );
646 | defaultConfigurationIsVisible = 0;
647 | defaultConfigurationName = Release;
648 | };
649 | /* End XCConfigurationList section */
650 | };
651 | rootObject = 607FACC81AFB9204008FA782 /* Project object */;
652 | }
653 |
--------------------------------------------------------------------------------
/Example/TipSee.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/TipSee.xcodeproj/xcshareddata/xcschemes/TipSee-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
56 |
62 |
63 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
86 |
88 |
94 |
95 |
96 |
97 |
103 |
105 |
111 |
112 |
113 |
114 |
116 |
117 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/Example/TipSee.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/TipSee.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/TipSee/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TipSee
4 | //
5 | // Created by farshadjahanmanesh on 07/16/2019.
6 | // Copyright (c) 2019 farshadjahanmanesh. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | UINavigationBar.appearance().barStyle = .black
19 |
20 | return true
21 | }
22 |
23 | func applicationWillResignActive(_ application: UIApplication) {
24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
26 | }
27 |
28 | func applicationDidEnterBackground(_ application: UIApplication) {
29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
31 | }
32 |
33 | func applicationWillEnterForeground(_ application: UIApplication) {
34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
35 | }
36 |
37 | func applicationDidBecomeActive(_ application: UIApplication) {
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 | func applicationWillTerminate(_ application: UIApplication) {
42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
43 | }
44 |
45 |
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/Example/TipSee/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Example/TipSee/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
102 |
120 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/978146509981.imageset/978146509981.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/978146509981.imageset/978146509981.jpg
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/978146509981.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "978146509981.jpg"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/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" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/bottom.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Screen Shot 2019-10-31 at 11.03.32 PM.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/bottom.imageset/Screen Shot 2019-10-31 at 11.03.32 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/bottom.imageset/Screen Shot 2019-10-31 at 11.03.32 PM.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/dog.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Screen Shot 2019-10-31 at 11.00.25 PM.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/dog.imageset/Screen Shot 2019-10-31 at 11.00.25 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/dog.imageset/Screen Shot 2019-10-31 at 11.00.25 PM.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/edit.imageset/001-pencil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/edit.imageset/001-pencil.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/edit.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "001-pencil.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/eggs.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Screen Shot 2019-11-01 at 9.59.07 AM.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/eggs.imageset/Screen Shot 2019-11-01 at 9.59.07 AM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/eggs.imageset/Screen Shot 2019-11-01 at 9.59.07 AM.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/fashion-profile_2x.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "fashion-profile_2x.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/fashion-profile_2x.imageset/fashion-profile_2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/fashion-profile_2x.imageset/fashion-profile_2x.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/galaxy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "realistic-galaxy-background_52683-12122.jpg"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/galaxy.imageset/realistic-galaxy-background_52683-12122.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/galaxy.imageset/realistic-galaxy-background_52683-12122.jpg
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/heart-like.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "heart-like.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/heart-like.imageset/heart-like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/heart-like.imageset/heart-like.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/left-arrow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "left-arrow.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/left-arrow.imageset/left-arrow.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/left-arrow.imageset/left-arrow.pdf
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/moon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "moon-icon-flat-01-.jpg"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/moon.imageset/moon-icon-flat-01-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/moon.imageset/moon-icon-flat-01-.jpg
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/photo.imageset/002-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/photo.imageset/002-add.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/photo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "002-add.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "timothy-paul-smith-256424-1200x800.jpg"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/profile.imageset/timothy-paul-smith-256424-1200x800.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/profile.imageset/timothy-paul-smith-256424-1200x800.jpg
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/pug_puppy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "pug_puppy.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/pug_puppy.imageset/pug_puppy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/pug_puppy.imageset/pug_puppy.png
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/right-arrow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "right-arrow.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/right-arrow.imageset/right-arrow.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/right-arrow.imageset/right-arrow.pdf
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/sunny.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "sunny-176-781177.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/TipSee/Images.xcassets/sunny.imageset/sunny-176-781177.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/Images.xcassets/sunny.imageset/sunny-176-781177.png
--------------------------------------------------------------------------------
/Example/TipSee/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UIStatusBarStyle
34 | UIStatusBarStyleDarkContent
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 |
40 | UIViewControllerBasedStatusBarAppearance
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Example/TipSee/TipSee_Example-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 |
--------------------------------------------------------------------------------
/Example/TipSee/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TipSee
4 | //
5 | // Created by farshadjahanmanesh on 07/16/2019.
6 | // Copyright (c) 2019 farshadjahanmanesh. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import TipSee
11 |
12 | class ViewController: UIViewController {
13 | @IBOutlet weak var bigBottomButton : UIButton!
14 | @IBOutlet weak var noConstraintsButton : UIButton!
15 | @IBOutlet weak var transformedButton : UIImageView!
16 | @IBOutlet weak var pugImage : UIView!
17 | @IBOutlet weak var pugName : UIView!
18 | @IBOutlet weak var pugDescrription : UIView!
19 | private var tips : TipSeeManager?
20 | private var rotationDegree : CGFloat = 45
21 | private var startDayAndNight = false
22 |
23 | @IBAction func reShowTips(){
24 | self.tips?.finish()
25 | self.showTips()
26 | }
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 |
30 | bigBottomButton.titleLabel?.lineBreakMode = .byWordWrapping
31 | bigBottomButton.titleLabel?.numberOfLines = 0
32 |
33 | if #available(iOS 10.0, *) {
34 | let t = Timer.scheduledTimer(withTimeInterval: 0.75, repeats: true) { [unowned self](_) in
35 | UIView.animate(withDuration: 0.75) {
36 |
37 | if self.startDayAndNight {
38 | if self.rotationDegree < 0 {
39 | // day
40 | self.transformedButton.image = UIImage(named: "sunny")
41 | self.tips?.pointer.options.dimColor = UIColor(red:0.97, green:0.76, blue:0.29, alpha:1.0)
42 | }else{
43 | // night
44 | self.transformedButton.image = UIImage(named: "moon")
45 | self.tips?.pointer.options.dimColor = UIColor(red:0.02, green:0.18, blue:0.23, alpha:1.0)
46 | }
47 | }
48 | self.transformedButton.transform = self.transformedButton.transform
49 | .rotated(by: self.rotationDegree)
50 | }
51 | }
52 | t.fire()
53 | }
54 | }
55 |
56 | private var pugLoveConfig: TipSee.Options.Bubble {
57 | return TipSee.Options.Bubble
58 | .default()
59 | .with{
60 | $0.foregroundColor = .white
61 | $0.textAlignments = .justified
62 | $0.position = .top
63 |
64 | }
65 | }
66 |
67 | private var pugDescriptionConfig : TipSee.Options.Bubble {
68 | return TipSee.Options.Bubble
69 | .default()
70 | .with{
71 | $0.backgroundColor = UIColor.purple
72 | $0.foregroundColor = UIColor.white
73 | $0.position = .left
74 | }
75 | }
76 |
77 | private func showTips() {
78 |
79 | let image = UIImageView(image: #imageLiteral(resourceName: "heart-like.png"))
80 | image.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
81 | image.contentMode = .scaleAspectFit
82 | let transformed = TipSee.Options.Bubble
83 | .default()
84 | .with{
85 | $0.backgroundColor = UIColor.orange
86 | $0 .foregroundColor = .white
87 | $0 .textAlignments = .left
88 | $0 .position = .right
89 | //$0.changeDimColor = UIColor(red:0.02, green:0.18, blue:0.23, alpha:1.0)
90 | }
91 |
92 | let defaultTipOption = TipSee.Options
93 | .default()
94 | .with {
95 | $0.dimColor = UIColor.black.withAlphaComponent(0.3)
96 | $0.bubbleLiveDuration = .untilNext
97 | $0.dimFading = false
98 | }
99 |
100 | self.tips = TipSeeManager(on: self.view.window!,with: defaultTipOption)
101 |
102 | tips!.add(
103 | new: self.pugImage,
104 | texts: [
105 | "We can show interactive tips on top of views or anything else conforming to `TipTarget`.",
106 | "The positioning of the tip bubble (or custom view) is automatically calculated by TipSee for you.",
107 | "This decision is based on the size of the tip and the available space around it.",
108 | "Alternatively, the tip position can be explicity set for precise control."
109 | ],
110 | with: pugLoveConfig)
111 | {
112 | previousButton, nextButton in
113 | nextButton.imageView?.contentMode = .scaleAspectFit
114 | previousButton.imageView?.contentMode = .scaleAspectFit
115 | nextButton.setImage(#imageLiteral(resourceName: "right-arrow.pdf"), for: .normal)
116 | previousButton.setImage(#imageLiteral(resourceName: "left-arrow.pdf"), for: .normal)
117 | previousButton.tintColor = .white
118 | nextButton.tintColor = .white
119 | }
120 |
121 | tips!.add(new: self.pugImage, text: "Best dog ever <3 <3 ^_^ ^_^", with: pugDescriptionConfig.with{$0.position = .right})
122 |
123 | addAttributedTextTip()
124 |
125 | tips!.add(new: self.pugName, text: "My name is leo ^_^", with: pugDescriptionConfig.with{
126 | $0.position = .top
127 | if #available(iOS 10.0, *) {
128 | $0.backgroundColor = UIColor(displayP3Red: 0.451, green: 0.807, blue: 0.317, alpha: 1)
129 | } else {
130 | // Fallback on earlier versions
131 | }
132 | })
133 |
134 | tips!.add( new: self.pugDescrription,text: "I am single and looking for my soulmate", with: pugDescriptionConfig.with{
135 | $0.position = .bottom
136 | if #available(iOS 10.0, *) {
137 | $0.backgroundColor = UIColor(displayP3Red: 0.451, green: 0.807, blue: 0.317, alpha: 1)
138 | } else {
139 | // Fallback on earlier versions
140 | }
141 | })
142 |
143 | tips!.add(new: self.transformedButton,text: "Please tap on the \(rotationDegree < 0 ? "☀️" : "🌑").", with: transformed.with{
144 | $0.position = .left
145 | $0.changeDimColor = UIColor(red:0.02, green:0.18, blue:0.23, alpha:1.0)
146 | $0.onTargetAreaTap = {[weak self] item in
147 | guard var degree = self?.rotationDegree else {return}
148 | self?.startDayAndNight = true
149 | degree = (degree * -1)
150 | self?.rotationDegree = degree
151 | guard let label = item.contentView as? UILabel else {
152 | return
153 | }
154 | label.text = "Please tap on the \(degree < 0 ? "☀️" : "🌑")"
155 | }
156 | $0.onBubbleTap = {[unowned self]_ in
157 | self.startDayAndNight = false
158 | self.tips?.pointer.options = defaultTipOption
159 | }
160 | })
161 |
162 | tips!.add(new: self.noConstraintsButton,text: "Hi!", with:transformed.with{ $0.backgroundColor = .red })
163 |
164 | tips!.add(
165 | new: SimpleTipTarget(on: CGRect(x: UIScreen.main.bounds.midX - 50, y: UIScreen.main.bounds.midY - 50, width: 100, height: 100),cornerRadius: 50),
166 | text: "Tip with no target view, just an arbitrary fixed position", with: transformed.with{$0.backgroundColor = .red})
167 |
168 | tips!.add(
169 | new: self.bigBottomButton,
170 | text: "A long piece of tip text which needs to span over multiple lines. This tip text will only fit if placed above the target area. This placement is provided for us by TipSee.",
171 | with: transformed.with {
172 | $0.onTargetAreaTap = { [weak self]_ in
173 | guard let degree = self?.rotationDegree else {return}
174 | self?.rotationDegree = (degree * -1)
175 | }
176 | $0.backgroundColor = UIColor.black
177 | $0.finishOnTargetAreaTap = true
178 | }
179 | )
180 |
181 | tips!.onBubbleTap = {[unowned tips] _ in
182 | tips?.next()
183 | }
184 |
185 | tips!.onDimTap = {[unowned self] _ in
186 | self.startDayAndNight = false
187 | guard let tips = self.tips else {return}
188 | tips.pointer.options = defaultTipOption
189 | if let index = tips.currentIndex,tips.tips.count == (index + 1) {
190 | tips.finish()
191 | DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
192 | self.tips = nil
193 | })
194 | }
195 |
196 | tips.next()
197 | }
198 | self.tips!.next()
199 | }
200 |
201 | override func viewDidAppear(_ animated: Bool) {
202 | super.viewDidAppear(animated)
203 | self.navigationController?.navigationBar.barStyle = .black
204 |
205 | self.showTips()
206 | }
207 |
208 | private func addAttributedTextTip() {
209 |
210 | let boldAttributes = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 15, weight: .bold)]
211 | let normalAttributes = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 15, weight: .regular)]
212 | let puppiesText = NSMutableAttributedString(string: "Soon to have some ", attributes: normalAttributes)
213 | let boldStringPart = NSMutableAttributedString(string: " puppies", attributes: boldAttributes)
214 |
215 | puppiesText.append(boldStringPart)
216 |
217 | // create attachment
218 | let imageAttachment = NSTextAttachment()
219 | imageAttachment.image = UIImage(named: "pug_puppy")
220 |
221 | // wrap the attachment in its own attributed string so we can append it
222 | let imageString = NSAttributedString(attachment: imageAttachment)
223 |
224 | // add the NSTextAttachment wrapper to our string, then add some more text.
225 | puppiesText.append(imageString)
226 | puppiesText.append(imageString)
227 | puppiesText.append(imageString)
228 | puppiesText.append(NSAttributedString(string: " !!!"))
229 |
230 | tips!.add(new: self.pugImage, text: puppiesText, with: pugDescriptionConfig.with{$0.position = .right})
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/Example/TipSee/images/TipSee.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/images/TipSee.gif
--------------------------------------------------------------------------------
/Example/TipSee/images/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Example/TipSee/images/logo.jpg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 farshadjahanmanesh
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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "TipSee",
8 | platforms: [.iOS(.v9)],
9 | products: [
10 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
11 | .library(
12 | name: "TipSee",
13 | targets: ["TipSee"]),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
22 | .target(
23 | name: "TipSee",
24 | dependencies: []),
25 | .testTarget(
26 | name: "TipSeeTests",
27 | dependencies: ["TipSee"]),
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # TipSee
7 | ### TipSee is a lightweight and highly customizable library that helps you to show beautiful tips and hints.
8 |
9 | [](https://travis-ci.org/farshadjahanmanesh/TipSee)
10 | [](https://cocoapods.org/pods/TipSee)
11 | [](https://cocoapods.org/pods/TipSee)
12 | [](https://cocoapods.org/pods/TipSee)
13 |
14 | ### To do:
15 |
16 | - [x] Live change
17 | - [x] Touchable target area
18 | - [x] Animating between tips
19 | - [x] Multipe Tip in the screen
20 | - [x] Dim animation
21 | - [x] bubble animation
22 | - [x] multipleTip, one Location
23 | - [ ] it is good for tip to follow it's target area movements
24 |
25 | # What we can do with TipSee?
26 | We can show interactive hints on top of the views or where ever we want but finding the best place to put the bubble (or custom view) is based on the TipSee's decision. it will find best place to show the hint by considering the available space and the content size, smartly. we can show custom views (like that heart) or simple text as you've seen in the Gif. Tips can point to all kind of views like button, images and ... or just a specific part of the view controller(like the hint that points to the center of the view in the gif)
27 |
28 |
29 | # Customization options:
30 | There are two types of configuration;
31 | * Global, base configuration of all tips within a given instance of TipSee - **TipSee.Options**
32 | * Individual tip configuration - **TipSee.Options.Bubble**
33 |
34 | If a both a base and per tip configuration is set, then the per tip configuration will take precendence.
35 |
36 | **Global Options**
37 | ```swift
38 | public struct Options: TipSeeConfiguration {
39 |
40 | /// buble's options, bubbles will get the default if nothings set
41 | public var bubbles: Bubble
42 |
43 | /// default dim's color, each bubble could changes this color(optionaly) by setting the bubble.dimBackgroundColor
44 | public var dimColor: UIColor
45 |
46 | /// bubble's life cycle.
47 | /// forEver : bubbles will be visible and needs to be remove manualy by caliing dismiss(item), you can show multiple bubbles same time
48 | /// untilNext: everytime show() function is called, previous bubble(if exists) will remove and new one will present
49 | public var bubbleLiveDuration: BubbleLiveDuration
50 |
51 | /// indicates the default bubble's position, each bubble can has specific position by setting bubble.position
52 | public var defaultBubblePosition: BubblePosition
53 |
54 | /// specifies the hole's(Target Area) radius
55 | /// keepTargetAreaCornerRadius : uses target view layer corner radius
56 | /// constantRadius(radius) : sets constant radius for all
57 | /// defaultOrGreater(default) : sets a constant default value or uses the target view layer corner radius if it is greater that the default value
58 | /// none : no corner rradius
59 | public var holeRadius: HoleRadius
60 |
61 | /// indicates bubble's margin
62 | public var safeAreaInsets: UIEdgeInsets
63 |
64 | /// if true, dim will fade after one second
65 | public var dimFading: Bool
66 |
67 | /// default is false. It true, touches on the dimmed area will be passed through
68 | public var shouldPassTouchesThroughDimmingArea: Bool
69 |
70 | public var holePositionChangeDuration: TimeInterval
71 | }
72 | ```
73 | **Bubble Options**
74 | ```swift
75 | public struct Bubble: TipSeeConfiguration {
76 |
77 | /// bubble's background color
78 | public var backgroundColor: UIColor
79 |
80 | /// preferred position for the bubble
81 | public var position: BubblePosition?
82 |
83 | /// text's font
84 | public var font: UIFont
85 |
86 | /// text's color
87 | public var foregroundColor: UIColor
88 |
89 | /// text's alignment
90 | public var textAlignments: NSTextAlignment
91 |
92 | /// bubble's appearance animation (bounce + fade-in)
93 | public var hasAppearAnimation: Bool
94 |
95 | /// distance between the bubble and the target view
96 | public var padding: UIEdgeInsets = .zero
97 |
98 | /// Whole tip (dimming and bubble) should be dismissed when user taps on the target area.
99 | public var finishOnTargetAreaTap: Bool
100 |
101 | /// default is false. It true, touches on target area will be passed through
102 | public var shouldPassTouchesThroughTargetArea: Bool
103 |
104 | /// will execute when user taps on target area
105 | public var onTargetAreaTap: TapGesture?
106 |
107 | /// each tip could has a different dim color
108 | public var changeDimColor: UIColor?
109 |
110 | /// Whole tip (dimming and bubble) should be dismissed when user taps on the bubble.
111 | public var finishOnBubbleTap: Bool
112 |
113 | /// will execute when user taps on the bubble
114 | public var onBubbleTap: TapGesture?
115 | }
116 | ```
117 |
118 | ## Actions
119 | TipSee has four actions which we can react to them to handle some situations
120 | 1. **Bubble.onBubbleTap** when user clicks on the bubble view, we can access to tapped bubble and item
121 | 2. **Bubble.onTargetAreaTap** when user click on target area
122 | 3. **HintObject.onDimTap** when user clicks on dim(background), we can access to latest hint on the screen
123 | 4. **HintObject.onBubbleTap** when user clicks on bubble, this is default action if the bubble has not specified one for itself
124 |
125 | ## Tip Lifecycle
126 | based on what we set for **bubbleLiveDuration** in options, tips have a life duration which means that tips should be on the screen until user taps on them to dismiss or should be removed before new one is appearing
127 |
128 | ## How To Use
129 | first thing is setuping your option (or using default option) and then creating a new instance of TipSee
130 | ```swift
131 | let defaultTipOption = TipSee.Options
132 | .default()
133 | .with {
134 | $0.dimColor = UIColor.black.withAlphaComponent(0.3)
135 | $0.bubbleLiveDuration = .untilNext
136 | $0.dimFading = false
137 | }
138 |
139 | // TipSee needs a window to show it's content in it
140 | let tipsee = TipSee(on: self.view.window!)
141 |
142 | // shows a simple text(tip) that is pointed to the view(pugImage), this tip has not specific configuration
143 | // so it will use default one
144 | tipSee.show(for: self.pugImage, text: "good boy")
145 | ```
146 |
147 | or you can create a custom tip with customized configuration
148 | ```swift
149 | // creates new custom item with custom configs
150 | let pugLoveConfig = TipSee.Options.Bubble
151 | .default()
152 | .with{
153 | $0.backgroundColor = .clear
154 | $0.foregroundColor = .black
155 | $0.textAlignments = .left
156 | $0.padding = UIEdgeInsets.init(top: 0, left: 16, bottom: 0, right: 16)
157 | $0.position = .top
158 | }
159 | tipSee.show(item TipSee.TipItem.init(ID: "100", pointTo: self.pugImage, contentView: image,bubbleOptions: pugLoveConfig))
160 |
161 | ```
162 |
163 | in above exmaple wee need to handle tip sequence ourselves. next, previous, presenting or dismissing should be handled by using the actions like bubble tap, target area tap, dimtap and ... but there is a slideshow extension which we talk in neext section that helps us with these things.
164 |
165 | ## TipSeeManager
166 | TipSee manager is a helper class that gives us the ability to have a slideshow like Tips. this manager handles tips array and provides handy apis (next, previous). we can add tips as many as we want and then start the sequence by calling **.next()**
167 |
168 | ```swift
169 | let defaultTipOption = TipSee.Options
170 | .default()
171 | .with {
172 | $0.dimColor = UIColor.black.withAlphaComponent(0.3)
173 | $0.bubbleLiveDuration = .untilNext
174 | $0.dimFading = false
175 | }
176 |
177 | let tipManager = TipcManager(on: self.view.window!,with: defaultTipOption)
178 | tipManager.add(new: TipSee.TipItem.init(ID: "100", pointTo: self.pugImage, contentView: image,bubbleOptions: pugLoveConfig))
179 | tipManager.add(new: self.pugImage,text:"best dog ever <3 <3 ^_^ ^_^",with: pugDescriptionConfig.with{$0.position = .right})
180 | tipManager.add...
181 | ...
182 | ...
183 | ...
184 |
185 | tipManager.next()
186 |
187 | ```
188 |
189 |
190 | ## Example
191 |
192 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
193 |
194 | ## Requirements
195 | this library doe's not need any requirement and has written in Swift5.
196 | ## Installation
197 |
198 | #### Using [CocoaPods](https://cocoapods.org)
199 |
200 | Edit your `Podfile` and specify the dependency:
201 |
202 | ```ruby
203 | pod 'TipSee'
204 | ```
205 |
206 | #### Using [Swift Package Manager](https://github.com/apple/swift-package-manager)
207 |
208 | Once you have your Swift package set up, adding `TipSee` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
209 |
210 | ```swift
211 | dependencies: [
212 | .package(url: "https://github.com/farshadjahanmanesh/TipSee.git", from: "1.6.0")
213 | ]
214 | ```
215 |
216 | ## Author
217 | farshadjahanmanesh, farshadjahanmanesh@gmail.com
218 |
219 | ## Contributors
220 | [lawmaestro](https://github.com/lawmaestro)
221 |
222 | ## License
223 |
224 | TipSee is available under the MIT license. See the LICENSE file for more info. the demo's design is something that i've took from dribbble and is blong to https://dribbble.com/harshgopal
225 |
--------------------------------------------------------------------------------
/Sources/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/valentinjahanmanesh/TipSee/5e7c780ddcfe8a6609a72190a545d53cbdd1f561/Sources/Assets/.gitkeep
--------------------------------------------------------------------------------
/Sources/TipSee/BubbleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BubbleView.swift
3 | // TipSee
4 | //
5 | // Created by Farshad Jahanmanesh on 8/14/19.
6 | //
7 |
8 | import UIKit
9 |
10 | public class BubbleView: UIView {
11 | public private(set) var insideView: UIView?
12 | private let cornerRadius: CGFloat = 4
13 | private lazy var shape: CAShapeLayer = {
14 | let shape = CAShapeLayer()
15 | shape.rasterizationScale = UIScreen.main.scale
16 | shape.shouldRasterize = true
17 | return shape
18 | }()
19 |
20 | var backColor: UIColor? {
21 | didSet { setNeedsLayout() }
22 | }
23 |
24 | public struct Arrow {
25 | public struct Position {
26 | public enum Distance {
27 | case mid(offset:CGFloat)
28 | case constant(_ offset:CGFloat)
29 | }
30 | public var distance: Distance
31 | public var edge: CGRectEdge
32 | }
33 | public var position: Position
34 | public var size: CGSize
35 | }
36 |
37 | public var arrow: Arrow = .init(position: .init(distance: .constant(0), edge: .minXEdge), size: .zero) {
38 | didSet { setNeedsLayout() }
39 | }
40 |
41 | override init(frame: CGRect) {
42 | super.init(frame: frame)
43 | //default value
44 | backgroundColor = .clear//UIColor(red: 0.18, green: 0.52, blue: 0.92, alpha: 1.0)
45 |
46 | configureView()
47 | }
48 |
49 | required init?(coder aDecoder: NSCoder) {
50 | fatalError("init(coder:) has not been implemented")
51 | }
52 |
53 | private func configureView() {
54 | layer.insertSublayer(shape, at: 0)
55 | backgroundColor = .clear
56 | layer.shadowOpacity = 0.2
57 | layer.shadowOffset = CGSize(width: 0, height: 1)
58 | layer.shadowRadius = 1.0
59 | layer.shadowColor = UIColor.black.cgColor
60 | }
61 |
62 | func setContent(view: UIView, padding: CGFloat = 0) {
63 | //remove all subviews
64 | self.subviews.forEach {
65 | $0.removeFromSuperview()
66 | }
67 |
68 | self.addSubview(view)
69 | insideView = view
70 | if self.translatesAutoresizingMaskIntoConstraints {
71 | view.frame.origin = CGPoint(x: padding, y: padding)
72 | view.frame.size = self.frame.insetBy(dx: padding, dy: padding).size
73 | view.removeConstraints(view.constraints)
74 | }else {
75 | self.translatesAutoresizingMaskIntoConstraints = true
76 | view.translatesAutoresizingMaskIntoConstraints = false
77 | if #available(iOS 11.0, *) {
78 | view.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -padding).isActive = true
79 | view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: padding).isActive = true
80 | view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -padding).isActive = true
81 | view.leftAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leftAnchor, constant: padding).isActive = true
82 |
83 | } else {
84 | view.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -padding).isActive = true
85 | view.topAnchor.constraint(equalTo: self.topAnchor, constant: padding).isActive = true
86 | view.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -padding).isActive = true
87 | view.leftAnchor.constraint(equalTo: self.leftAnchor, constant: padding).isActive = true
88 | }
89 | }
90 | }
91 |
92 | private func getStandardArrowCenterOffset() -> CGFloat {
93 | let minOffset = arrow.size.width/2 + cornerRadius
94 | let viewWidth = self.frame.width
95 | switch arrow.position.distance {
96 | case .mid(let offset):
97 | switch arrow.position.edge {
98 | case .maxYEdge, .minYEdge:
99 | return max(minOffset, min(offset + self.bounds.midX, self.frame.width - minOffset))
100 | default :
101 | return max(minOffset, min(offset + self.bounds.midY, self.frame.height - minOffset))
102 | }
103 | case .constant(let offset):
104 | return max(minOffset, min(offset, viewWidth - minOffset))
105 | }
106 | }
107 |
108 | public override func layoutSubviews() {
109 | super.layoutSubviews()
110 |
111 | let path = UIBezierPath()
112 |
113 | let height = self.frame.height
114 | let width = self.frame.width
115 | let minX = self.bounds.minX
116 | let minY = self.bounds.minY
117 | let arrowWidth = arrow.size.width
118 | let arrowHeight = arrow.size.height
119 | let offset = getStandardArrowCenterOffset()
120 |
121 | switch arrow.position.edge {
122 | case .minYEdge:
123 | path.move(to: CGPoint(x: minX + offset - arrowWidth/2, y: minY))
124 | path.addLine(to: CGPoint(x: minX + offset, y: minY - arrowHeight))
125 | path.addLine(to: CGPoint(x: minX + offset + arrowWidth/2, y: minY))
126 | case .maxYEdge:
127 | path.move(to: CGPoint(x: minX + offset - arrowWidth/2, y: minY + height))
128 | path.addLine(to: CGPoint(x: minX + offset, y: minY + height + arrowHeight))
129 | path.addLine(to: CGPoint(x: minX + offset + arrowWidth/2, y: minY + height))
130 | case .maxXEdge:
131 | path.move(to: CGPoint(x: minX + width, y: minY + offset - arrowWidth/2))
132 | path.addLine(to: CGPoint(x: minX + width + arrowHeight, y: minY + offset))
133 | path.addLine(to: CGPoint(x: minX + width, y: minY + offset + arrowWidth/2))
134 | default:
135 | path.move(to: CGPoint(x: minX, y: minY + offset - arrowWidth/2))
136 | path.addLine(to: CGPoint(x: minX - arrowHeight, y: minY + offset))
137 | path.addLine(to: CGPoint(x: minX, y: minY + offset + arrowWidth/2))
138 | }
139 |
140 | let roundedRectPath = UIBezierPath(
141 | roundedRect: CGRect(x: minX, y: minY, width: width, height: height),
142 | cornerRadius: cornerRadius
143 | )
144 |
145 | path.append(roundedRectPath)
146 | shape.fillColor = backColor?.cgColor
147 | shape.path = path.cgPath
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/Sources/TipSee/NSAttributedString+Size.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+Size.swift
3 | // TipSee
4 | //
5 | // Created by Adam Law on 01/03/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | extension NSAttributedString {
11 | func height(widthConstraint: CGFloat) -> CGFloat {
12 | let rect = self.boundingRect(with: CGSize(width: widthConstraint, height: CGFloat.greatestFiniteMagnitude),
13 | options: [.usesLineFragmentOrigin, .usesFontLeading],
14 | context: nil)
15 | return ceil(rect.size.height)
16 | }
17 | func width(widthConstraint: CGFloat, heightConstraint: CGFloat) -> CGFloat {
18 | let rect = self.boundingRect(with: CGSize(width: widthConstraint, height: heightConstraint),
19 | options: [.usesLineFragmentOrigin, .usesFontLeading],
20 | context: nil)
21 | return ceil(rect.size.width)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipItem.swift
3 | // TipSee
4 | //
5 | // Created by Farshad Jahanmanesh on 8/14/19.
6 | //
7 |
8 | import UIKit
9 |
10 | extension TipSee.TipItem {
11 | public static func == (lhs: TipSee.TipItem, rhs: TipSee.TipItem) -> Bool {
12 | return lhs.ID == rhs.ID
13 | }
14 | }
15 |
16 | extension TipSee {
17 | public struct TipItem: Equatable {
18 | public typealias ID = String
19 | public var ID: ID
20 | public var pointTo: AnyTipTraget
21 | public var contentView: UIView
22 | public var bubbleOptions: TipSee.Options.Bubble?
23 | public init(ID: ID = UUID().uuidString, pointTo: TipTarget, contentView: UIView) {
24 | self.ID = ID
25 | self.pointTo = AnyTipTraget.init(tipTarget: pointTo)
26 | self.contentView = contentView
27 | }
28 |
29 |
30 | public init(ID: ID = UUID().uuidString, pointTo: TipTarget, contentView: UIView, bubbleOptions: TipSee.Options.Bubble?) {
31 | self.ID = ID
32 | self.pointTo = AnyTipTraget.init(tipTarget: pointTo)
33 | self.contentView = contentView
34 | self.bubbleOptions = bubbleOptions
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipOption.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipOption.swift
3 | // TipSee
4 | //
5 | // Created by Farshad Jahanmanesh on 8/14/19.
6 | //
7 |
8 | import UIKit
9 |
10 | extension TipSee {
11 |
12 | public enum BubbleLiveDuration {
13 | case forever
14 | case untilNext
15 | // case until(second:TimeInterval)
16 | }
17 |
18 | public enum HoleRadius {
19 | /// uses target view layer corner radius
20 | case keepTargetAreaCornerRadius
21 |
22 | /// sets constant radius for all
23 | case constantRadius(radius: CGFloat)
24 |
25 | /// sets a constant default value or uses the target view layer corner radius if it is greater that the default value
26 | case defaultOrGreater(default: CGFloat)
27 |
28 | /// no corner rradius
29 | case none
30 | }
31 |
32 | public struct Options: TipSeeConfiguration {
33 | public typealias BubblePosition = UIRectEdge
34 | public struct Bubble: TipSeeConfiguration {
35 |
36 | /// bubble's background color
37 | public var backgroundColor: UIColor
38 |
39 | /// preferred position for the bubble
40 | public var position: BubblePosition?
41 |
42 | /// text's font
43 | public var font: UIFont
44 |
45 | /// text's color
46 | public var foregroundColor: UIColor
47 |
48 | /// text's alignment
49 | public var textAlignments: NSTextAlignment
50 |
51 | /// bubble's appearance animation (bounce + fade-in)
52 | public var hasAppearAnimation: Bool
53 |
54 | /// distance between the bubble and the target view
55 | public var padding: UIEdgeInsets = .zero
56 |
57 | /// Whole tip (dimming and bubble) should be dismissed when user taps on the target area.
58 | public var finishOnTargetAreaTap: Bool
59 |
60 | /// default is false. It true, touches on target area will be passed through
61 | public var shouldPassTouchesThroughTargetArea: Bool
62 |
63 | /// will execute when user taps on target area
64 | public var onTargetAreaTap: TapGesture?
65 |
66 | /// each tip could has a different dim color
67 | public var changeDimColor: UIColor?
68 |
69 | /// Whole tip (dimming and bubble) should be dismissed when user taps on the bubble.
70 | public var finishOnBubbleTap: Bool
71 |
72 | /// will execute when user taps on the bubble
73 | public var onBubbleTap: TapGesture?
74 |
75 | public static func `default`()->TipSee.Options.Bubble {
76 | return Options.Bubble(
77 | backgroundColor: .red,
78 | position: nil,
79 | font: UIFont.boldSystemFont(ofSize: 15),
80 | foregroundColor: UIColor.white,
81 | textAlignments: .center,
82 | hasAppearAnimation: true,
83 | padding: .init(top: 16, left: 16, bottom: 16, right: 16),
84 | finishOnTargetAreaTap: false,
85 | shouldPassTouchesThroughTargetArea: false,
86 | onTargetAreaTap: nil,
87 | changeDimColor: nil,
88 | finishOnBubbleTap: false,
89 | onBubbleTap: nil)
90 | }
91 | }
92 |
93 | /// buble's options, bubbles will get the default if nothings set
94 | public var bubbles: Bubble
95 |
96 | /// default dim's color, each bubble could changes this color(optionaly) by setting the bubble.dimBackgroundColor
97 | public var dimColor: UIColor
98 |
99 | /// bubble's life cycle.
100 | /// forEver : bubbles will be visible and needs to be remove manualy by caliing dismiss(item), you can show multiple bubbles same time
101 | /// untilNext: everytime show() function is called, previous bubble(if exists) will remove and new one will present
102 | public var bubbleLiveDuration: BubbleLiveDuration
103 |
104 | /// indicates the default bubble's position, each bubble can has specific position by setting bubble.position
105 | public var defaultBubblePosition: BubblePosition
106 |
107 | /// specifies the hole's(Target Area) radius
108 | /// keepTargetAreaCornerRadius : uses target view layer corner radius
109 | /// constantRadius(radius) : sets constant radius for all
110 | /// defaultOrGreater(default) : sets a constant default value or uses the target view layer corner radius if it is greater that the default value
111 | /// none : no corner rradius
112 | public var holeRadius: HoleRadius
113 |
114 | /// indicates bubble's margin
115 | public var safeAreaInsets: UIEdgeInsets
116 |
117 | /// if true, dim will fade after one second
118 | public var dimFading: Bool
119 |
120 | /// default is false. It true, touches on the dimmed area will be passed through
121 | public var shouldPassTouchesThroughDimmingArea: Bool
122 |
123 | public var holePositionChangeDuration: TimeInterval
124 | public static func `default`()->TipSee.Options {
125 | return Options(
126 | bubbles: Options.Bubble.default(),
127 | dimColor: UIColor.black.withAlphaComponent(0.7),
128 | bubbleLiveDuration: .forever,
129 | defaultBubblePosition: .left,
130 | holeRadius: .defaultOrGreater(default: 8),
131 | safeAreaInsets: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16),
132 | dimFading: true,
133 | shouldPassTouchesThroughDimmingArea: false,
134 | holePositionChangeDuration: 0.5)
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipSee+AttributedText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSee+AttributedText.swift
3 | // TipSee
4 | //
5 | // Created by Adam Law on 02/03/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension TipSee {
12 |
13 | /// shows a bubble which points to the given view
14 | ///
15 | /// - Parameters:
16 | /// - item: the view that we want to point at and a text for bubble
17 | /// - bubbleOption: custom options for bubble
18 | /// - Returns: generated item that can use to access to views or dismiss action
19 | @discardableResult
20 | public func show(for view: TipTarget, text string: NSAttributedString, with bubbleOption: Options.Bubble? = nil) -> TipItem {
21 | let viewToShow = createItem(for: view, text: string, with: bubbleOption)
22 | return self.show(item: viewToShow, with: bubbleOption)
23 | }
24 |
25 | @discardableResult
26 | public func createItem(for target: TipTarget, text: NSAttributedString, with bubbleOption: Options.Bubble? = nil) -> TipItem {
27 | return TipItem(ID: UUID().uuidString, pointTo: target, contentView: TipSee.createLabel(for: text, with: bubbleOption, defaultOptions: .default()) as UIView, bubbleOptions: bubbleOption)
28 | }
29 |
30 | /// creates a labelView for useing inside a bubble
31 | ///
32 | /// - Parameter text: label text
33 | /// - Returns: generated label view
34 | private static func createLabel(
35 | for text: NSAttributedString,
36 | with itemOptions: Options.Bubble?,
37 | defaultOptions options: TipSee.Options
38 | ) -> UILabel
39 | {
40 | let label = UILabel()
41 | label.attributedText = text
42 | label.textAlignment = .center
43 | label.lineBreakMode = .byWordWrapping
44 | label.numberOfLines = 0
45 | label.font = itemOptions?.font ?? options.bubbles.font
46 | label.sizeToFit()
47 | label.textColor = itemOptions?.foregroundColor ?? options.bubbles.foregroundColor
48 | return label
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipSee.swift:
--------------------------------------------------------------------------------
1 | // TipSee.swift
2 | // TipSee
3 | //
4 | // Created by Farshad Jahanmanesh on 7/2/19.
5 | // Copyright © 2019 Tap30. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | public class TipSee: UIView, TipSeeManagerProtocol {
11 | public typealias TapGesture = ((TipItem) -> Void)
12 | /// properties
13 | public var options: Options = Options.default() {
14 | didSet{
15 | if options.dimColor != oldValue.dimColor {
16 | observeForDimColorChange()
17 | }
18 | }
19 | }
20 |
21 | fileprivate var shadowLayerPath: CGPath?
22 | fileprivate unowned let _window: UIWindow
23 | fileprivate lazy var views: [TipItem] = {
24 | attachToWindow()
25 | return [TipItem]()
26 | }()
27 |
28 | fileprivate lazy var bubbles = { return [BubbleView]() } ()
29 | private var isBubbleAnimationInProgress = false
30 | private var isHoleAnimationInProgress = false
31 | private var isAnimationInProgress: Bool {
32 | return isHoleAnimationInProgress || isBubbleAnimationInProgress
33 | }
34 |
35 | fileprivate var latestTip: TipItem?
36 |
37 | /// in a very odd situation, hit test called twice and we want to prevent multiple calls to our functions
38 | fileprivate var touched: (view:UIView?, timeStamp: Date?)
39 |
40 |
41 | public var onBubbleTap: TapGesture?
42 | public var onDimTap: TapGesture?
43 | public var onFinished: (() -> Void)?
44 |
45 | /// shows a bubble which points to the given view
46 | ///
47 | /// - Parameters:
48 | /// - item: the view that we want to point at and a text for bubble
49 | /// - bubbleOption: custom options for bubble
50 | /// - Returns: generated item that can use to access to views or dismiss action
51 | @discardableResult public func show(for view: TipTarget,text string: String, with bubbleOption: Options.Bubble? = nil) -> TipItem {
52 | let viewToShow = createItem(for: view, text: string, with: bubbleOption)
53 | return self.show(item: viewToShow, with: bubbleOption)
54 | }
55 |
56 | @discardableResult public func createItem(for target: TipTarget, text: String, with bubbleOption: Options.Bubble? = nil) -> TipItem {
57 | return TipItem(ID: UUID().uuidString, pointTo: target, contentView: TipSee.createLabel(for: text, with: bubbleOption, defaultOptions: self.options) as UIView, bubbleOptions: bubbleOption)
58 | }
59 | @discardableResult public static func createItem(for target: TipTarget, text: String, with bubbleOption: Options.Bubble? = nil) -> TipItem {
60 | return TipItem(ID: UUID().uuidString, pointTo: target, contentView: TipSee.createLabel(for: text, with: bubbleOption, defaultOptions: .default()) as UIView, bubbleOptions: bubbleOption)
61 | }
62 |
63 | private final func clearAllViews() {
64 | guard !views.isEmpty else{return}
65 | views.forEach { (item) in
66 | self.dismiss(item: item)
67 | }
68 | }
69 |
70 | private final func store(tip: TipItem, bubble: BubbleView) {
71 | self.latestTip = tip
72 | self.views.append(tip)
73 | self.bubbles.append(bubble)
74 | }
75 |
76 | private final func deStore(index: Int) {
77 | self.latestTip = nil
78 | if self.bubbles.count > index {
79 | self.bubbles.remove(at: index)
80 | }
81 | if self.views.count > index{
82 | self.views.remove(at: index)
83 | }
84 | }
85 |
86 | /// shows a bubble which points to the given view
87 | ///
88 | /// - Parameters:
89 | /// - item: the view that you want to point at and a view that will show inside the bubble
90 | /// - bubbleOption: custom options for bubble
91 | /// - Returns: generated item that can use to access to views or dismiss action
92 | @discardableResult public func show(item: TipItem, with bubbleOption: Options.Bubble? = nil) -> TipItem {
93 | let tip = TipItem(ID: item.ID.isEmpty ? UUID().uuidString: item.ID, pointTo: item.pointTo, contentView: item.contentView as UIView, bubbleOptions: bubbleOption ?? item.bubbleOptions)
94 | if options.bubbleLiveDuration == .untilNext {
95 | clearAllViews()
96 | }
97 | store(tip: tip, bubble: self.point(to: tip))
98 | createHoleForVisibleViews()
99 | return tip
100 | }
101 |
102 | private final func attachToWindow() {
103 | self.frame = _window.frame
104 | self.tag = 9891248
105 | _window.addSubview(self)
106 | _window.bringSubviewToFront(self)
107 | }
108 |
109 | /// has animation
110 | public func finish() {
111 | UIView.animateKeyframes(withDuration: 0.2, delay: 0, options: [], animations: {
112 | self.alpha = 0
113 | }) { done in
114 | if done {
115 | self.removeFromSuperview()
116 | }
117 | self.onFinished?()
118 | }
119 | }
120 |
121 | /// removes the given item. it will finish tipPointer after dismissing if this is the last item in tips array, if you plan to show another item on dimissal(one by one) create an array of items and add them to the tipPointer and set the bubbleLiveDuration == .untilNext.
122 | ///
123 | /// - Parameter item: item to remove
124 | public func dismiss(item: TipItem) {
125 | if let index = self.views.lastIndex(where: {$0 == item}) {
126 | let bubble = self.bubbles[index]
127 | bubble.removeFromSuperview()
128 | deStore(index: index)
129 | createHoleForVisibleViews()
130 | }
131 | }
132 |
133 | private final func createHoleForVisibleViews() {
134 | //shadowLayer?.removeFromSuperlayer()
135 | guard superview != nil else { return }
136 | let pathBigRect = UIBezierPath(rect: superview!.frame)
137 | if self.views.isEmpty {
138 | return
139 | }
140 |
141 | ///
142 | var startPoint: CGPoint?
143 | let viewsSet = Set(self.views.map({$0.pointTo}))
144 | viewsSet.forEach { (targetArea) in
145 | // cuts a hole inside the layer
146 | let cutPosition = targetArea.tipFrame.insetBy(dx: -4, dy: -4)
147 | if startPoint == nil {
148 | startPoint = CGPoint(x: cutPosition.midX, y: cutPosition.midY)
149 | }
150 | var cornerRadius: CGFloat = 0
151 | switch options.holeRadius {
152 | case .constantRadius(radius: let radius):
153 | cornerRadius = radius
154 | case .defaultOrGreater(default: let radius):
155 | cornerRadius = max(radius, targetArea.cornersRadius)
156 | case .keepTargetAreaCornerRadius :
157 | cornerRadius = targetArea.cornersRadius
158 | default:
159 | cornerRadius = 0
160 | }
161 | pathBigRect.append(UIBezierPath(roundedRect: cutPosition.insetBy(dx: -4, dy: -4), cornerRadius: cornerRadius))
162 | }
163 | pathBigRect.usesEvenOddFillRule = true
164 | options.dimColor = latestTip?.bubbleOptions?.changeDimColor ?? options.dimColor
165 | self.cutHole(for: pathBigRect.cgPath, startPoint: startPoint)
166 | }
167 |
168 | public init(on window: UIWindow) {
169 | self._window = window
170 | super.init(frame: .zero)
171 | let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapDim(_:)))
172 | self.addGestureRecognizer(tap)
173 | }
174 |
175 | @objc
176 | private func tapDim(_ sender: UITapGestureRecognizer) {
177 | guard let latestTip = latestTip else {
178 | assertionFailure("here, POINTER have to have a tip, so check if something wrong")
179 | return
180 | }
181 | onDimTap?(latestTip)
182 | }
183 |
184 | public required init?(coder aDecoder: NSCoder) {
185 | fatalError("init(coder:) has not been implemented")
186 | }
187 |
188 | @objc
189 | private func tapBubble(_ sender: UITapGestureRecognizer) {
190 | guard let item = self.views.first(where: { item in item.ID == sender.identifier }) else {
191 | assertionFailure("Here we have to have that bubble(tip) but we can not find it")
192 | return
193 | }
194 | if let customGesture = item.bubbleOptions?.onBubbleTap {
195 | customGesture(item)
196 | } else {
197 | onBubbleTap?(item)
198 | }
199 | let option = item.bubbleOptions ?? self.options.bubbles
200 | if option.finishOnBubbleTap {
201 | self.finish()
202 | }
203 | }
204 |
205 | /// points to the given(target) view by constrainting/Positioning the bubbleView and furthermore adds animation to newborn bubble
206 | ///
207 | /// - Parameter item: tip item
208 | /// - Returns: baked bubble view
209 | private final func point(to item: TipItem) -> BubbleView {
210 | self.latestTip = item
211 | let view = item.pointTo
212 | let label = item.contentView
213 | let bubble = defaultBubble(for: item, defaultOptions: options)
214 | .setProperSizeWith(content: label)
215 |
216 | self.addSubview(bubble)
217 |
218 | let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapBubble(_:)))
219 | tap.identifier = item.ID
220 | bubble.addGestureRecognizer(tap)
221 |
222 | // animates the bubble appearing
223 | if let animation = item.bubbleOptions?.hasAppearAnimation, animation {
224 | bubble.transform = .init(scaleX: 0.5, y: 0.5)
225 | self.animateBubble {
226 | bubble.transform = .identity
227 | bubble.alpha = 1
228 | }
229 | } else {
230 | self.animateBubble {
231 | bubble.alpha = 1
232 | }
233 | }
234 |
235 | // binds constraints
236 | let arrowInstalledPosition = self.setBubbleConstraints(for: bubble, to: item)
237 | bubble.setContent(view: label, padding: 8)
238 | self.layoutIfNeeded()
239 |
240 | // align the arrow
241 | let center = CGPoint(x: view.tipFrame.midX, y: view.tipFrame.midY)
242 |
243 | if [.top, .bottom].contains(arrowInstalledPosition) {
244 | bubble.arrow = .init(position: .init(distance: .constant(center.x - bubble.frame.origin.x), edge: arrowInstalledPosition.toCGRectEdge()), size: .init(width: 10, height: 5))
245 | } else {
246 | bubble.arrow = .init(position: .init(distance: .mid(offset:0), edge: arrowInstalledPosition.toCGRectEdge()), size: .init(width: 10, height: 5))
247 | }
248 |
249 | return bubble
250 | }
251 |
252 | private func animateBubble(with: @escaping () -> Void) {
253 | isBubbleAnimationInProgress = true
254 | UIView.animate(withDuration: 0.5, delay: 0.2, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: .curveLinear, animations: {
255 | with()
256 | }, completion: { [weak self] _ in
257 | self?.isBubbleAnimationInProgress = false
258 | })
259 | }
260 |
261 | /// you can choose your prefered position for bubble to be shown, but sometimes there are no enough space there for showing that bubble, this will find a better place for bubbleView
262 | ///
263 | /// - Parameters:
264 | /// - view: target view
265 | /// - Returns: the better edge
266 | private func findBetterSpace(view: TipTarget, preferredPosition: UIRectEdge?, bubblePrefereddSize: CGRect?) -> UIRectEdge {
267 | let reletivePosition = view.tipFrame
268 |
269 | var edges = [(UIRectEdge, Bool)]()
270 |
271 | var left = options.safeAreaInsets.left + options.bubbles.padding.right + 16 + (bubblePrefereddSize?.width ?? 0)
272 |
273 | var right = _window.bounds.width - (options.safeAreaInsets.right + options.bubbles.padding.left + 16) - (bubblePrefereddSize?.width ?? 0)
274 |
275 | var top = options.safeAreaInsets.top + options.bubbles.padding.bottom + 16 + (bubblePrefereddSize?.height ?? 0)
276 |
277 | var bottom = _window.bounds.height - ( options.safeAreaInsets.bottom + options.bubbles.padding.top + 16) + (bubblePrefereddSize?.height ?? 0)
278 | if #available(iOS 11.0, *) {
279 | bottom = _window.bounds.height - (_window.safeAreaInsets.bottom + options.safeAreaInsets.bottom + options.bubbles.padding.top + 16) + (bubblePrefereddSize?.height ?? 0)
280 |
281 | top = options.safeAreaInsets.top + options.bubbles.padding.bottom + 16 + _window.safeAreaInsets.top + (bubblePrefereddSize?.height ?? 0)
282 |
283 | right = _window.bounds.width - (_window.safeAreaInsets.right + options.safeAreaInsets.right + options.bubbles.padding.left + 16) - (bubblePrefereddSize?.width ?? 0)
284 |
285 | left = options.safeAreaInsets.left + options.bubbles.padding.right + 16 + _window.safeAreaInsets.left + (bubblePrefereddSize?.width ?? 0)
286 | }
287 |
288 | edges.append((.left, reletivePosition.minX > left))
289 |
290 | edges.append((.top, reletivePosition.minY > top))
291 |
292 | edges.append((.right, reletivePosition.maxX < right))
293 |
294 | edges.append((.bottom, reletivePosition.maxY < bottom))
295 |
296 | guard let doIHaveEnoughSpace = edges.first(where: {$0.0 == preferredPosition ?? options.defaultBubblePosition}), doIHaveEnoughSpace.1 else {
297 | return edges.first(where: {$0.0 != preferredPosition ?? options.defaultBubblePosition && $0.1})?.0 ?? options.defaultBubblePosition
298 | }
299 | return preferredPosition ?? options.defaultBubblePosition
300 | }
301 |
302 | /// sets constraints for bubble view and the taget view
303 | ///
304 | /// - Parameters:
305 | /// - view: target view
306 | /// - bubble: bubble view
307 | /// - padding: space between bubble view and target view
308 | /// - Returns: bubble view arrow position
309 | private func setBubbleConstraints(for bubble: BubbleView, to item: TipItem) -> UIRectEdge {
310 | let view = item.pointTo
311 | let preferredPosition = item.bubbleOptions?.position
312 | let padding = item.bubbleOptions?.padding ?? UIEdgeInsets.all(16)
313 | let position = findBetterSpace(view: view, preferredPosition: preferredPosition, bubblePrefereddSize: bubble.frame)
314 | var arrowPoint: UIRectEdge = .right
315 |
316 | let targetFrame = view.tipFrame
317 | let controllerSize = self._window.bounds.size
318 | switch position {
319 | case .left:
320 | arrowPoint = .right
321 | bubble.frame.size = findBubbleProperSize(for: item.contentView, on: CGSize(width: abs(targetFrame.minX - (options.safeAreaInsets.totalX + padding.left)), height: controllerSize.height - options.safeAreaInsets.totalY))
322 |
323 | bubble.frame.origin = CGPoint(x: targetFrame.minX - (padding.right + bubble.frame.size.width), y: targetFrame.midY - bubble.frame.midY)
324 | case .right:
325 | arrowPoint = .left
326 | bubble.frame.size = findBubbleProperSize(for: item.contentView, on: CGSize(width: (controllerSize.width - (options.safeAreaInsets.totalX + padding.left + targetFrame.maxX)), height: controllerSize.height))
327 | bubble.frame.origin = CGPoint(x: targetFrame.minX + targetFrame.size.width + padding.left, y: targetFrame.midY - bubble.frame.midY)
328 | case .bottom:
329 | arrowPoint = .top
330 | bubble.frame.origin.y = targetFrame.maxY + padding.top
331 | bubble.center.x = view.tipFrame.midX
332 |
333 | case .top:
334 | arrowPoint = .bottom
335 | bubble.frame.origin.y = targetFrame.minY - bubble.frame.size.height - padding.bottom
336 | bubble.center.x = targetFrame.midX
337 | default:
338 | break
339 | }
340 | if bubble.frame.minX < options.safeAreaInsets.left {
341 | bubble.frame.origin.x = options.safeAreaInsets.left
342 | }
343 | if bubble.frame.maxX > controllerSize.width - options.safeAreaInsets.right {
344 | bubble.frame.origin.x -= ((bubble.frame.maxX - controllerSize.width) + options.safeAreaInsets.right)
345 | }
346 | return arrowPoint
347 | }
348 |
349 | private func observeForDimColorChange(){
350 | guard let shadowPath = self.layer.sublayers?[0] as? CAShapeLayer,shadowPath.fillColor != options.dimColor.cgColor else {
351 | return
352 | }
353 | let pathAnimation = basicAnimation(key: "fillColor", duration: 0.2)
354 | pathAnimation.fromValue = shadowPath.fillColor
355 | pathAnimation.toValue = options.dimColor.cgColor
356 | shadowPath.add(pathAnimation, forKey: nil)
357 | shadowPath.fillColor = options.dimColor.cgColor
358 | }
359 |
360 | private final func cutHole(for path: CGPath, startPoint: CGPoint? = nil) {
361 |
362 | let fillLayer = CAShapeLayer()
363 |
364 | fillLayer.fillRule = CAShapeLayerFillRule.evenOdd
365 | fillLayer.fillColor = options.dimColor.cgColor
366 |
367 | guard let shadowPath = self.layer.sublayers?[0] as? CAShapeLayer else {
368 | self.layer.insertSublayer(fillLayer, at: 0)
369 | let height = 2 * max(_window.bounds.width, _window.bounds.height)
370 | var circleRect = CGRect(origin: startPoint ?? .zero, size: .init(width: height, height: height))
371 | circleRect.origin.x -= height / 2
372 | circleRect.origin.y -= height / 2
373 | let base = UIBezierPath(rect: _window.bounds)
374 |
375 | base.append( UIBezierPath(roundedRect: circleRect, cornerRadius: height / 2))
376 | base.usesEvenOddFillRule = true
377 | addHoleAnimations(on: fillLayer, old: base.cgPath, new: path,force: true)
378 | fillLayer.path = path
379 | shadowLayerPath = path
380 | return
381 | }
382 |
383 | shadowPath.path = path
384 | addHoleAnimations(on: shadowPath, old: shadowLayerPath!, new: path)
385 | shadowLayerPath = path
386 | }
387 |
388 | private final func addHoleAnimations(on layer: CAShapeLayer, old: CGPath, new: CGPath, force: Bool = false) {
389 | let pathAnimation = basicAnimation(
390 | key: "path",
391 | duration: self.options.holePositionChangeDuration,
392 | completion: { [weak self] in
393 | self?.isHoleAnimationInProgress = false
394 | }
395 | )
396 | pathAnimation.fromValue = options.bubbleLiveDuration == .untilNext ? old : new
397 | pathAnimation.toValue = new
398 | layer.add(pathAnimation, forKey: nil)
399 | isHoleAnimationInProgress = true
400 |
401 | if options.dimFading {
402 | let fillColorAnimation = basicAnimation(key: "fillColor", duration: 1)
403 | fillColorAnimation.toValue = UIColor.clear.cgColor
404 | fillColorAnimation.beginTime = CACurrentMediaTime() + 2
405 | layer.add(fillColorAnimation, forKey: nil)
406 | }
407 | }
408 | private final func basicAnimation(key: String, duration: TimeInterval, completion: (() -> Void)? = nil ) -> CABasicAnimation {
409 |
410 | let animation = CABasicAnimation(keyPath: key)
411 | animation.duration = duration
412 | animation.isRemovedOnCompletion = false
413 | animation.fillMode = CAMediaTimingFillMode.forwards
414 |
415 | CATransaction.setCompletionBlock {
416 | completion?()
417 | }
418 | return animation
419 | }
420 | }
421 |
422 | fileprivate extension UIRectEdge {
423 | func toCGRectEdge() -> CGRectEdge {
424 | switch self {
425 | case .top:
426 | return .minYEdge
427 | case .bottom :
428 | return .maxYEdge
429 | case .right :
430 | return .maxXEdge
431 | default:
432 | return .minXEdge
433 | }
434 | }
435 | }
436 |
437 | fileprivate extension UIEdgeInsets {
438 | static func all(_ value: CGFloat) -> UIEdgeInsets {
439 | return UIEdgeInsets(top: value, left: value, bottom: value, right: value)
440 | }
441 |
442 | var totalX: CGFloat {
443 | return self.left + self.right
444 | }
445 |
446 | var totalY: CGFloat {
447 | return self.top + self.bottom
448 | }
449 | }
450 |
451 | extension String {
452 | func height(font: UIFont, widthConstraint: CGFloat) -> CGFloat {
453 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: widthConstraint, height: CGFloat.greatestFiniteMagnitude))
454 | label.numberOfLines = 0
455 | label.lineBreakMode = NSLineBreakMode.byWordWrapping
456 | label.font = font
457 | label.text = self + " "
458 |
459 | label.sizeToFit()
460 | return label.frame.height
461 | }
462 | func width(font: UIFont, widthConstraint: CGFloat, heightConstraint: CGFloat) -> CGFloat {
463 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: widthConstraint, height: heightConstraint))
464 | label.numberOfLines = 0
465 | label.lineBreakMode = NSLineBreakMode.byWordWrapping
466 | label.font = font
467 | label.text = self + " "
468 |
469 | label.sizeToFit()
470 | return label.frame.width
471 | }
472 | }
473 |
474 | /// Overrides HitTest and Point inside
475 | extension TipSee {
476 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
477 | guard self.touched.timeStamp == nil else {
478 | let touchedView = self.touched.view
479 | self.touched = (nil,nil)
480 | return touchedView
481 | }
482 | let hitView = super.hitTest(point, with: event)
483 | self.touched = (hitView, Date())
484 |
485 | return hitView
486 | }
487 |
488 | public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
489 | guard shadowLayerPath != nil, let latestTip = latestTip else {
490 | return false
491 | }
492 | if isAnimationInProgress {
493 | // Consume the touch and stop it going any further.
494 | // This ensures tips can't be cycled through / dismissed mid animation which
495 | // can otherwise lead to tips overlaying a mismatched target screen / layout.
496 | return true
497 | }
498 | let targetArea = latestTip.pointTo
499 | let insetTargetArea = targetArea.tipFrame.insetBy(dx: -4, dy: -4)
500 |
501 | let isInTargetArea = insetTargetArea.contains(point)
502 | let option = latestTip.bubbleOptions ?? self.options.bubbles
503 |
504 | if isInTargetArea {
505 | option.onTargetAreaTap?(latestTip)
506 | if option.finishOnTargetAreaTap {
507 | self.finish()
508 | }
509 |
510 | if option.shouldPassTouchesThroughTargetArea == false {
511 | // Consume the touch
512 | return true
513 | }
514 | return false
515 | }
516 |
517 | let isInBubbleArea = bubbles.last?.frame.contains(point) ?? false
518 | if isInBubbleArea {
519 | // Consume the touch
520 | return true
521 | }
522 |
523 | let isInDimmingArea = frame.contains(point)
524 | if isInDimmingArea {
525 | if options.shouldPassTouchesThroughDimmingArea == false {
526 | // Consume the touch
527 | return true
528 | }
529 | return false
530 | }
531 | return false
532 | }
533 | }
534 |
535 | private extension UITapGestureRecognizer {
536 |
537 | private struct AssociatedKeys {
538 | static var identifier = "identifier"
539 | }
540 |
541 | var identifier: String {
542 | get {
543 | return (objc_getAssociatedObject(self, &AssociatedKeys.identifier) as? String) ?? ""
544 | } set {
545 | objc_setAssociatedObject(
546 | self,
547 | &AssociatedKeys.identifier,
548 | newValue,
549 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC
550 | )
551 | }
552 | }
553 | }
554 |
555 | extension TipSee {
556 |
557 | /// creates a default bubble
558 | ///
559 | /// - Parameter item: tip item
560 | /// - Returns: bubble view
561 | final func defaultBubble(for item: TipSee.TipItem, defaultOptions options: TipSee.Options) -> BubbleView {
562 |
563 | let bubble = BubbleView.default()
564 | bubble.backColor = item.bubbleOptions?.backgroundColor ?? options.bubbles.backgroundColor
565 | return bubble
566 | }
567 |
568 | /// creates a labelView for useing inside a bubble
569 | ///
570 | /// - Parameter text: label text
571 | /// - Returns: generated label view
572 | private static func createLabel(for text: String, with itemOptions: Options.Bubble?, defaultOptions options: TipSee.Options) -> UILabel {
573 | let label = UILabel()
574 | label.text = text
575 | label.textAlignment = .center
576 | label.lineBreakMode = .byWordWrapping
577 | label.numberOfLines = 0
578 | label.sizeToFit()
579 | label.font = itemOptions?.font ?? options.bubbles.font
580 | label.textColor = itemOptions?.foregroundColor ?? options.bubbles.foregroundColor
581 | return label
582 | }
583 |
584 |
585 | public func options(_ options: TipSee.Options) {
586 | self.options = options
587 | }
588 | /// finds bubble size
589 | ///
590 | /// - Parameters:
591 | /// - availableSpace: avialable space for bubble to fit in
592 | /// - view: view that live in bubble view
593 | /// - Returns: proper size
594 | private func findBubbleProperSize(for view: UIView,on availableSpace: CGSize? = nil) -> CGSize {
595 | var calculatedFrame = CGSize.zero
596 | let availableSpace = availableSpace ?? CGSize(width: UIScreen.main.bounds.width - (64), height: UIScreen.main.bounds.height)
597 | if let label = view as? UILabel, let text = label.attributedText {
598 | calculatedFrame.height = text.height(widthConstraint: availableSpace.width) + 16
599 | calculatedFrame.width = text.width(widthConstraint: availableSpace.width, heightConstraint: self.frame.size.height) + 16
600 | }else {
601 | calculatedFrame = view.frame.insetBy(dx: -8, dy: -8).size
602 | }
603 | return calculatedFrame
604 | }
605 | }
606 |
607 | extension BubbleView {
608 | /// finds bubble size
609 | ///
610 | /// - Parameters:
611 | /// - availableSpace: avialable space for bubble to fit in
612 | /// - view: view that live in bubble view
613 | /// - Returns: updated bubble view
614 | fileprivate func setProperSizeWith(content view: UIView,on availableSpace: CGSize? = nil) -> BubbleView {
615 | let bubbleView = self
616 | var calculatedFrame = CGSize.zero
617 | let availableSpace = availableSpace ?? CGSize(width: UIScreen.main.bounds.width - (64), height: UIScreen.main.bounds.height)
618 | if let label = view as? UILabel, let text = label.attributedText {
619 | calculatedFrame.height = text.height(widthConstraint: availableSpace.width) + 16
620 | calculatedFrame.width = text.width(widthConstraint: availableSpace.width, heightConstraint: self.frame.size.height) + 16
621 | }else {
622 | calculatedFrame = view.frame.insetBy(dx: -8, dy: -8).size
623 | bubbleView.translatesAutoresizingMaskIntoConstraints = false
624 | }
625 | bubbleView.frame.size = calculatedFrame
626 | return bubbleView
627 | }
628 |
629 | /// creates a default bubble
630 | ///
631 | /// - Parameter item: tip item
632 | /// - Returns: bubble view
633 | fileprivate static func `default`() -> BubbleView {
634 | let bubble = BubbleView(frame: .zero)
635 | bubble.alpha = 0
636 | bubble.arrow = .init(position: .init(distance: .mid(offset: 0), edge: UIRectEdge.left.toCGRectEdge()), size: .init(width: 10, height: 5))
637 | return bubble
638 | }
639 | }
640 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipSeeConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSeeConfiguration.swift
3 | // TipSee
4 | //
5 | // Created by Farshad on 10/30/19.
6 | //
7 |
8 | public protocol TipSeeConfiguration {
9 | func with(_ mutations: (inout Self) -> Void) -> Self
10 | }
11 |
12 | extension TipSeeConfiguration {
13 | public func with(_ mutations: (inout Self) -> Void) -> Self {
14 | var copyOfSelf = self
15 | mutations(©OfSelf)
16 | return copyOfSelf
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipSeeManager+AttributedText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSeeManager+AttributedText.swift
3 | // TipSee
4 | //
5 | // Created by Adam Law on 03/01/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | extension TipSeeManager {
11 |
12 | /// Adds a tip which targets a given object conforming to `TipTarget`
13 | /// - Parameters:
14 | /// - target: Target object used for pinning the tip to.
15 | /// - string: Attributed tip text to show in the bubble.
16 | /// - bubbleOptions: Bubble appearance configuration options.
17 | public func add(
18 | new target: TipTarget,
19 | text string: NSAttributedString,
20 | with bubbleOptions: TipSee.Options.Bubble?)
21 | {
22 | self.tips.append(pointer.createItem(for: target, text: string, with: bubbleOptions))
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipSeeManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSeeManager.swift
3 | // TipSee
4 | //
5 | // Created by Farshad Jahanmanesh on 7/18/19.
6 | //
7 |
8 | import UIKit
9 |
10 | public final class TipSeeManager {
11 | public private(set) var pointer: TipSee
12 | public internal(set) var tips: [TipSee.TipItem]
13 | public private(set) var latestTip: TipSee.TipItem?
14 | public var onBubbleTap: ((TipSee.TipItem?) -> Void)? {
15 | didSet {
16 | pointer.onBubbleTap = self.onBubbleTap
17 | }
18 | }
19 | public var onDimTap: ((TipSee.TipItem?) -> Void)? {
20 | didSet {
21 | pointer.onDimTap = self.onDimTap
22 | }
23 | }
24 | public var onFinished: (() -> Void)? {
25 | didSet {
26 | pointer.onFinished = self.onFinished
27 | }
28 | }
29 | public private(set) var currentIndex: Int? {
30 | didSet {
31 | guard let index = currentIndex else {
32 | self.pointer.finish()
33 | return
34 | }
35 | latestTip = tips[index]
36 | self.pointer.show(item: latestTip!)
37 | }
38 | }
39 |
40 | /// Designated intializer.
41 | /// - Parameters:
42 | /// - window: The window used for attaching the tip view to.
43 | /// - options: Manager appearance configuration options.
44 | public init(on window: UIWindow, with options: TipSee.Options) {
45 | self.pointer = TipSee(on: window)
46 | self.pointer.options(options)
47 | self.tips = [TipSee.TipItem]()
48 | }
49 |
50 | /// Adds a tip which targets a given object conforming to `TipTarget`
51 | /// - Parameters:
52 | /// - view: Target object used for pinning the tip to.
53 | /// - string: Tip text to show in the bubble.
54 | /// - bubbleOptions: Bubble appearance configuration options.
55 | public func add(new view: TipTarget, text string: String, with bubbleOption: TipSee.Options.Bubble?) {
56 | self.tips.append(pointer.createItem(for: view,text: string, with: bubbleOption))
57 | }
58 |
59 | /// Adds a tip which targets a given object conforming to `TipTarget`
60 | /// - Parameters:
61 | /// - item: Target object used for pinning the tip to.
62 | public func add(new item: TipSee.TipItem) {
63 | self.tips.append(item)
64 | }
65 |
66 | /// shows the next tip
67 | public func next() {
68 | guard let current = latestTip, let currentIndex = tips.firstIndex(of: current) else {
69 | if !tips.isEmpty{
70 | self.currentIndex = 0
71 | }
72 | return
73 | }
74 | let next = currentIndex+1
75 | if next < tips.count {
76 | self.currentIndex = next
77 | } else {
78 | finish()
79 | }
80 | }
81 |
82 | // shows the previous tip
83 | public func previous() {
84 | guard let current = latestTip, let currentIndex = tips.firstIndex(of: current) else {
85 | return
86 | }
87 | let previous = currentIndex-1
88 | if previous >= 0 {
89 | self.currentIndex = previous
90 | }
91 | }
92 |
93 | public func finish() {
94 | pointer.finish()
95 | }
96 | }
97 |
98 | extension TipSeeManager {
99 |
100 | public func add(
101 | new view: TipTarget,
102 | texts strings: [String],
103 | with bubbleOption: TipSee.Options.Bubble?,
104 | buttonsConfigs: ((_ previousButton: UIButton, _ nextButton: UIButton) -> Void))
105 | {
106 | let buttonsHeight : CGFloat = 30
107 | let font = bubbleOption?.font ?? pointer.options.bubbles.font
108 | var containerHeight : CGFloat = 40
109 | var containerWidth : CGFloat = strings.max()?.width(font: font, widthConstraint: UIScreen.main.bounds.width - 48, heightConstraint: 40) ?? 100
110 |
111 | if containerWidth > UIScreen.main.bounds.width {
112 | containerWidth = UIScreen.main.bounds.width - 48
113 | }
114 | containerHeight = strings.max()?.height(font: font, widthConstraint: containerWidth) ?? 100
115 |
116 | let container = UIView(frame: CGRect(x: 0, y: 0, width: containerWidth, height: containerHeight + 40 ))
117 |
118 | // creates a scroll view and appends it to bubble view
119 | let customScrollView = UIScrollView(frame: .zero)
120 | customScrollView.translatesAutoresizingMaskIntoConstraints = false
121 | customScrollView.showsHorizontalScrollIndicator = false
122 | customScrollView.showsVerticalScrollIndicator = false
123 | container.addSubview(customScrollView)
124 | customScrollView.isScrollEnabled = false
125 | customScrollView.leftAnchor.constraint(equalTo: container.leftAnchor, constant: 0.0).isActive = true
126 | customScrollView.topAnchor.constraint(equalTo: container.topAnchor, constant: 0.0).isActive = true
127 | customScrollView.rightAnchor.constraint(equalTo: container.rightAnchor, constant: 0.0).isActive = true
128 | customScrollView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: buttonsHeight).isActive = true
129 | customScrollView.heightAnchor.constraint(equalToConstant: container.bounds.height - buttonsHeight).isActive = true
130 | customScrollView.widthAnchor.constraint(equalToConstant: container.bounds.width).isActive = true
131 |
132 | // creates a stack view and apeends it to scroll view
133 | let stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: container.frame.width, height: 200))
134 | customScrollView.addSubview(stackView)
135 | customScrollView.isPagingEnabled = true
136 | stackView.translatesAutoresizingMaskIntoConstraints = false
137 | stackView.leadingAnchor.constraint(equalTo: customScrollView.leadingAnchor).isActive = true
138 | stackView.trailingAnchor.constraint(equalTo: customScrollView.trailingAnchor).isActive = true
139 | stackView.topAnchor.constraint(equalTo: customScrollView.topAnchor).isActive = true
140 | stackView.bottomAnchor.constraint(equalTo: customScrollView.bottomAnchor).isActive = true
141 | stackView.heightAnchor.constraint(equalToConstant: container.bounds.height - buttonsHeight).isActive = true
142 | stackView.alignment = .fill
143 | stackView.distribution = .fillEqually
144 | stackView.axis = .horizontal
145 |
146 | // appends all texts as label inside stack view
147 | strings.forEach { (str) in
148 | let label = UILabel()
149 | label.text = str
150 | label.font = font
151 | label.lineBreakMode = .byWordWrapping
152 | label.numberOfLines = 0
153 | label.textColor = bubbleOption?.foregroundColor ?? pointer.options.bubbles.foregroundColor
154 | stackView.addArrangedSubview(label)
155 | label.widthAnchor.constraint(equalToConstant: container.frame.width).isActive = true
156 | }
157 |
158 | // creates next and previous button
159 | let leftButton = UIButton(frame: CGRect.init(origin: CGPoint(x: 0, y: container.frame.height - 25), size: CGSize(width: 25, height: 25)))
160 | leftButton.setTitleColor(.black, for: .normal)
161 | leftButton.accessibilityHint = "previous"
162 | leftButton.tag = 0
163 | leftButton.addTarget(self, action: #selector(scrollMultiLine(_:)), for: .touchUpInside)
164 | container.addSubview(leftButton)
165 |
166 | let rightButton = UIButton(frame: CGRect.init(origin: .zero, size: CGSize(width: 25, height: 25)))
167 | rightButton.setTitleColor(.black, for: .normal)
168 | rightButton.accessibilityHint = "next"
169 | rightButton.tag = 0
170 | rightButton.addTarget(self, action: #selector(scrollMultiLine(_:)), for: .touchUpInside)
171 | rightButton.frame.origin = CGPoint(x: container.frame.width - rightButton.frame.width, y: container.frame.height - 25)
172 | container.addSubview(rightButton)
173 |
174 | // creates a new item with new created conent view
175 | self.tips.append(.init(pointTo: view, contentView: container, bubbleOptions: bubbleOption))
176 |
177 | // return those two button so users can change the configurstions
178 | buttonsConfigs(leftButton, rightButton)
179 | }
180 |
181 | @objc
182 | private func scrollMultiLine(_ sender: UIButton) {
183 | guard let action = sender.accessibilityHint, let scrollView = sender.superview?.subviews.first(where: {$0 is UIScrollView }) as? UIScrollView else {
184 | return
185 | }
186 | var frame: CGRect = scrollView.frame
187 |
188 | var page = scrollView.tag
189 | page += action == "next" ? 1 : -1
190 | if page < 0 {
191 | return
192 | } else if (frame.size.width * CGFloat(page)) >= scrollView.contentSize.width {
193 | return
194 | }
195 | scrollView.tag = page
196 | print(page)
197 | frame.origin.x = frame.size.width * CGFloat(page)
198 | frame.origin.y = 0
199 | scrollView.scrollRectToVisible(frame, animated: true)
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipSeeManagerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSeeManagerProtocol.swift
3 | // TipSee
4 | //
5 | // Created by Farshad on 10/30/19.
6 | //
7 |
8 | public protocol TipSeeManagerProtocol {
9 | /// removes the given item
10 | ///
11 | /// - Parameter item: item to remove
12 | func dismiss(item: TipSee.TipItem)
13 |
14 | @discardableResult
15 | func show(for target: TipTarget, text string: String, with bubbleOption: TipSee.Options.Bubble?) -> TipSee.TipItem
16 |
17 | @discardableResult
18 | func createItem(for target: TipTarget, text string: String, with bubbleOption: TipSee.Options.Bubble?) -> TipSee.TipItem
19 |
20 | /// shows a bubble which points to the given view
21 | ///
22 | /// - Parameters:
23 | /// - item: the view that you want to point at and a view that will show inside the bubble
24 | /// - bubbleOption: custom options for bubble
25 | /// - Returns: generated item that can use to access to views or dismiss action
26 | @discardableResult
27 | func show(item: TipSee.TipItem, with bubbleOption: TipSee.Options.Bubble?) -> TipSee.TipItem
28 | func finish()
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/TipSee/TipTarget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipTarget.swift
3 | // TipSee
4 | //
5 | // Created by Farshad Jahanmanesh on 8/14/19.
6 | //
7 |
8 | import UIKit
9 |
10 | /// TipSee needs would interact with this type only
11 | public protocol TipTarget {
12 | var tipFrame : CGRect {get}
13 | var cornersRadius : CGFloat {get}
14 | }
15 | extension TipTarget where Self : Hashable {
16 | public static func ==(_ lhs : Self, rhs : Self)->Bool {
17 | return lhs.tipFrame == rhs.tipFrame
18 | }
19 | }
20 |
21 | extension UIView : TipTarget {
22 | public var cornersRadius: CGFloat {
23 | get {
24 | return self.layer.cornerRadius
25 | }
26 | }
27 |
28 | public var tipFrame: CGRect {
29 | guard let superView = self.superview else {
30 | return self.frame
31 | }
32 | let point = superView.convert(self.frame.origin, to: nil)
33 | return CGRect(origin: point, size: self.frame.size)
34 | }
35 | }
36 |
37 |
38 | /// TipTarget Type Erasure
39 | public struct AnyTipTraget : TipTarget , Hashable {
40 | public var tipFrame : CGRect {return _tipTarget.tipFrame}
41 | public var cornersRadius : CGFloat {return _tipTarget.cornersRadius}
42 | private var _tipTarget : TipTarget
43 |
44 | init(tipTarget : TipTarget){
45 | self._tipTarget = SimpleTipTarget(on: tipTarget.tipFrame, cornerRadius: tipTarget.cornersRadius)
46 | }
47 |
48 | public func hash(into hasher: inout Hasher) {
49 | hasher.combine(self.tipFrame.origin.x)
50 | hasher.combine(self.tipFrame.origin.y)
51 | hasher.combine(self.tipFrame.width)
52 | hasher.combine(self.tipFrame.height)
53 | hasher.combine(self.cornersRadius)
54 | }
55 | }
56 |
57 | public struct SimpleTipTarget : TipTarget {
58 | public var tipFrame: CGRect
59 | public var cornersRadius: CGFloat
60 | public init(on target : CGRect,cornerRadius : CGFloat){
61 | self.tipFrame = target
62 | self.cornersRadius = cornerRadius
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/TipSee/UIViewController+Exts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Exts.swift
3 | // TipSee
4 | //
5 | // Created by Farshad Jahanmanesh on 8/14/19.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIViewController {
11 | var tipManager : TipSee? {
12 | return self.view.window?.viewWithTag(9891248) as? TipSee
13 | }
14 |
15 | fileprivate static func swizzleMethods(original: Selector, swizzled: Selector) {
16 | guard
17 | let originalMethod = class_getInstanceMethod(self, original),
18 | let swizzledMethod = class_getInstanceMethod(self, swizzled) else { return }
19 | method_exchangeImplementations(originalMethod, swizzledMethod)
20 | }
21 |
22 | @objc private func swizzled_keyboardListener_viewDidDisappear(_ animated: Bool) {
23 | swizzled_keyboardListener_viewDidDisappear(animated)
24 | self.tipManager?.finish()
25 | }
26 |
27 | fileprivate static let TipSeeViewDidAppearSwizzler: Void = {
28 | swizzleMethods(original: #selector(viewDidDisappear(_:)),
29 | swizzled: #selector(swizzled_keyboardListener_viewDidDisappear))
30 | }()
31 | }
32 |
--------------------------------------------------------------------------------
/Tests/TipSeeTests/OptionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionsTests.swift
3 | // TipSee_Example
4 | //
5 | // Created by Farshad Jahanmanesh on 7/19/19.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import TipSee
11 |
12 | class OptionsTests: XCTestCase {
13 | var sut : TipSee!
14 |
15 | override func setUp() {
16 | super.setUp()
17 | let uiwindow = UIWindow()
18 | sut = TipSee(on: uiwindow)
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | sut = nil
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | super.tearDown()
26 | }
27 |
28 | func testSettingOptions(){
29 | // given
30 | sut.options = TipSee.Options.default()
31 |
32 | // when
33 | sut.options.dimColor = .red
34 |
35 | // then
36 | XCTAssertTrue(sut.options.dimColor == .red)
37 | }
38 |
39 | func testChangeBubbleOptions(){
40 | // given
41 | sut.options.bubbles = .default()
42 |
43 | // when
44 | sut.options.bubbles.backgroundColor = .yellow
45 |
46 | XCTAssertTrue(sut.options.bubbles.backgroundColor == .yellow)
47 | }
48 |
49 | func testHintPointerShouldHaveDefaultOptions(){
50 | XCTAssertNotNil(sut.options.bubbles, "Default bubble Option Should not be nil")
51 | XCTAssertNotNil(sut.options, "Default Option Should not be nil")
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Tests/TipSeeTests/TipItemTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipItemTests.swift
3 | // TipSee_Example
4 | //
5 | // Created by Farshad Jahanmanesh on 7/19/19.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import TipSee
11 |
12 | class TipItemTests: XCTestCase {
13 | let id = "55"
14 | var sut : TipSee.TipItem!
15 | var targetView : UIView!
16 | var bubbleContetView : UIView!
17 |
18 | override func setUp() {
19 | super.setUp()
20 | targetView = UIView()
21 | bubbleContetView = UIView()
22 | sut = TipSee.TipItem(ID: id, pointTo: targetView, contentView: bubbleContetView)
23 | // Put setup code here. This method is called before the invocation of each test method in the class.
24 | }
25 |
26 | override func tearDown() {
27 | sut = nil
28 | targetView = nil
29 | bubbleContetView = nil
30 | super.tearDown()
31 | // Put teardown code here. This method is called after the invocation of each test method in the class.
32 | }
33 |
34 | func testDefaultOption(){
35 | XCTAssertNil(sut.bubbleOptions)
36 | }
37 |
38 | func testCustomConfig(){
39 | // when
40 | sut.bubbleOptions = TipSee.Options.Bubble.default().with{
41 | $0.backgroundColor = .blue
42 | }
43 |
44 | XCTAssertNotNil(sut.bubbleOptions)
45 |
46 | XCTAssertNotNil(sut.bubbleOptions!.backgroundColor == .blue)
47 | }
48 |
49 | func testEquality(){
50 | // given
51 | var new = TipSee.TipItem(ID: "2", pointTo: UIView(), contentView: UIView())
52 |
53 | // when
54 | new.ID = id
55 |
56 | // then
57 | XCTAssertEqual(new, sut)
58 | }
59 |
60 | func testCustomConfigInInit(){
61 | // given
62 | let new = TipSee.TipItem(ID: "2", pointTo: UIView(), contentView: UIView(),bubbleOptions: TipSee.Options.Bubble.default().with{$0.backgroundColor = .blue})
63 |
64 | // when
65 | sut.bubbleOptions = TipSee.Options.Bubble.default().with{
66 | $0.backgroundColor = .blue
67 | }
68 |
69 | // then
70 | XCTAssertEqual(new.bubbleOptions!.backgroundColor, sut.bubbleOptions!.backgroundColor)
71 | }
72 | func testCustomBubbeTap(){
73 | // when
74 | sut.bubbleOptions = TipSee.Options.Bubble.default().with{
75 | $0.backgroundColor = .blue
76 | $0.onBubbleTap = {_ in }
77 | }
78 |
79 | // then
80 | XCTAssertNotNil(sut.bubbleOptions!.onBubbleTap)
81 | }
82 |
83 | func testCustomFont(){
84 | // given
85 | let new = TipSee.createItem(for: SimpleTipTarget(on: .zero,cornerRadius: 0), text: "XYS",with: TipSee.Options.Bubble.default().with{$0.font = .italicSystemFont(ofSize: 100)})
86 |
87 | // then
88 | XCTAssertEqual((new.contentView as! UILabel).font, UIFont.italicSystemFont(ofSize: 100))
89 | }
90 |
91 | func testMEMORYLEAK(){
92 |
93 | // given
94 | var xView : UIView? = UIView()
95 | let count = CFGetRetainCount(xView!)
96 | let _ = TipSee.TipItem(ID: "2", pointTo: xView!, contentView: UIView(),bubbleOptions: TipSee.Options.Bubble.default().with{$0.backgroundColor = .blue})
97 |
98 | let _ = TipSee.TipItem(ID: "2", pointTo: xView!, contentView: UIView(),bubbleOptions: TipSee.Options.Bubble.default().with{$0.backgroundColor = .blue})
99 |
100 | let _ = TipSee.TipItem(ID: "2", pointTo: xView!, contentView: UIView(),bubbleOptions: TipSee.Options.Bubble.default().with{$0.backgroundColor = .blue})
101 |
102 | let count2 = CFGetRetainCount(xView!)
103 | xView = nil
104 | // then
105 | XCTAssertEqual(count2, count)
106 | XCTAssertNil(xView)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Tests/TipSeeTests/TipSeeManagerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSeeManagerTests.swift
3 | // TipSee_Example
4 | //
5 | // Created by Adam Law on 01/03/2020.
6 | // Copyright © 2020 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import TipSee
11 |
12 | class TipSeeManagerTests: XCTestCase {
13 |
14 | private let window = UIWindow()
15 | private lazy var sut: TipSeeManager = {
16 | return TipSeeManager(on: window, with: TipSee.Options.default())
17 | }()
18 |
19 | override func setUp() {
20 | super.setUp()
21 | XCTAssertNil(sut.currentIndex)
22 | XCTAssertTrue(sut.tips.isEmpty)
23 | }
24 |
25 | func test_addTip() {
26 | sut.add(new: TestData.tipItem1)
27 | XCTAssertEqual(sut.tips.count, 1)
28 | }
29 |
30 | func test_addTip_withAttributedTextAndBubbleOptions() {
31 | let text = NSAttributedString.init(string: "Tip message")
32 | sut.add(new: TestData.target, text: text, with: TestData.bubbleOptions)
33 | XCTAssertEqual(sut.tips.count, 1)
34 | }
35 |
36 | func test_addTip_withBubbleOptions() {
37 | sut.add(new: TestData.target, text: "Tip message", with: TestData.bubbleOptions)
38 | XCTAssertEqual(sut.tips.count, 1)
39 | }
40 |
41 | func test_addTips_withBubbleOptions() {
42 | sut.add(
43 | new: TestData.target,
44 | texts: ["Tip message 1", "Tip message 2"],
45 | with: TestData.bubbleOptions
46 | ) { previousButton, nextButton in }
47 |
48 | XCTAssertEqual(sut.tips.count, 1)
49 | }
50 |
51 | func test_nextTip_withTwoTips() {
52 | sut.add(new: TestData.tipItem1)
53 | sut.add(new: TestData.tipItem2)
54 |
55 | sut.next()
56 | XCTAssertEqual(sut.currentIndex, 0)
57 | XCTAssertEqual(sut.latestTip, TestData.tipItem1)
58 |
59 | sut.next()
60 | XCTAssertEqual(sut.currentIndex, 1)
61 | XCTAssertEqual(sut.latestTip, TestData.tipItem2)
62 | }
63 |
64 | func test_nextTip_withNoTips() {
65 | sut.next()
66 | XCTAssertNil(sut.currentIndex)
67 | XCTAssertNil(sut.latestTip)
68 | }
69 |
70 | func test_previousTip_withTwoTips() {
71 | sut.add(new: TestData.tipItem1)
72 | sut.next()
73 | sut.add(new: TestData.tipItem2)
74 | sut.next()
75 |
76 | sut.previous()
77 | XCTAssertEqual(sut.currentIndex, 0)
78 | XCTAssertEqual(sut.latestTip, TestData.tipItem1)
79 |
80 | sut.previous()
81 | XCTAssertEqual(sut.currentIndex, 0)
82 | XCTAssertEqual(sut.latestTip, TestData.tipItem1)
83 | }
84 |
85 | func test_previousTip_withNoTips() {
86 | sut.previous()
87 | XCTAssertNil(sut.currentIndex)
88 | XCTAssertNil(sut.latestTip)
89 |
90 | sut.finish()
91 | }
92 |
93 | private struct TestData {
94 | static let target = SimpleTipTarget(on: CGRect.zero, cornerRadius: 0)
95 | static let bubbleOptions = TipSee.Options.Bubble.default()
96 | static let tipItem1 = TipSee.TipItem(pointTo: target, contentView: UIView())
97 | static let tipItem2 = TipSee.TipItem(pointTo: target, contentView: UIView())
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Tests/TipSeeTests/TipSeeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipSeeTests.swift
3 | // TipSee_Example
4 | //
5 | // Created by Farshad Jahanmanesh on 7/19/19.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import TipSee
11 |
12 | class TipSeeTests: XCTestCase {
13 | var sut : TipSee!
14 | var window : UIWindow!
15 | override func setUp() {
16 | super.setUp()
17 | window = UIWindow()
18 | sut = TipSee(on: window)
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | sut = nil
24 | window = nil
25 | super.tearDown()
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | }
28 |
29 | func testShownItemConfig(){
30 | let container = UIView()
31 | let targetView = UIView()
32 | container.addSubview(targetView)
33 | // given
34 | let item = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
35 |
36 | // when
37 | let givenItem = sut.show(item: item)
38 |
39 | // then
40 | XCTAssertEqual(givenItem.bubbleOptions!.backgroundColor , .green)
41 | }
42 |
43 | func testShownBubbleContent(){
44 | let container = UIView()
45 | let targetView = UIView()
46 | container.addSubview(targetView)
47 | // given
48 | let item = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
49 |
50 | // when
51 | let givenItem = sut.show(item: item)
52 |
53 | // then
54 | XCTAssertNotNil(givenItem.contentView as? UILabel)
55 | }
56 |
57 | func testShownBubbleContentText(){
58 | let container = UIView()
59 | let targetView = UIView()
60 | container.addSubview(targetView)
61 | // given
62 | let item = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
63 |
64 | // when
65 | let givenItem = sut.show(item: item)
66 |
67 | // then
68 | XCTAssert((givenItem.contentView as? UILabel)?.text == "hi")
69 | }
70 |
71 | func testShownDismissItem(){
72 | let container = UIView()
73 | let targetView = UIView()
74 | container.addSubview(targetView)
75 | // given
76 | let item = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
77 |
78 | // when
79 | sut.show(item: item)
80 |
81 | // then
82 | sut.dismiss(item: item)
83 | XCTAssert(sut.subviews.count == 0)
84 | }
85 |
86 | func testNoOrderDismiss() {
87 | let container = UIView()
88 | let targetView = UIView()
89 | container.addSubview(targetView)
90 | // given
91 | let item = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
92 | let item2 = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
93 |
94 | // when
95 | sut.show(item: item)
96 | sut.show(item: item2)
97 | // then
98 | sut.dismiss(item: item2)
99 | sut.dismiss(item: item2)
100 |
101 | XCTAssert(sut.subviews.count == 1)
102 | }
103 |
104 | func testItemMemoryLeak(){
105 | let container = UIView()
106 | let targetView = UIView()
107 | container.addSubview(targetView)
108 | // given
109 | let item = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
110 | var item2 : TipSee.TipItem? = sut.createItem(for: targetView,text:"hi",with: TipSee.Options.Bubble.default().with{$0.backgroundColor = .green})
111 |
112 | // when
113 | sut.show(item: item)
114 | sut.show(item: item2!)
115 | // then
116 |
117 | item2 = nil
118 | XCTAssertNil(item2)
119 | }
120 |
121 | public var options: TipSee.Options = TipSee.Options.default()
122 | private func findBetterSpace(view: TipTarget, preferredPosition: UIRectEdge?) -> UIRectEdge {
123 | let reletivePosition = view.tipFrame
124 |
125 | var edges = [(UIRectEdge, Bool)]()
126 |
127 | var left = options.safeAreaInsets.left + options.bubbles.padding.right + 16
128 |
129 | var right = UIScreen.main.bounds.width - (options.safeAreaInsets.right + options.bubbles.padding.left + 16)
130 |
131 | var top = options.safeAreaInsets.top + options.bubbles.padding.bottom + 16
132 |
133 | var bottom = UIScreen.main.bounds.height - (options.safeAreaInsets.bottom + options.bubbles.padding.top + 16)
134 | if #available(iOS 11.0, *) {
135 | bottom = UIScreen.main.bounds.height - (options.safeAreaInsets.bottom + options.bubbles.padding.top + 16)
136 |
137 | top = options.safeAreaInsets.top + options.bubbles.padding.bottom + 16
138 |
139 | right = UIScreen.main.bounds.width - (options.safeAreaInsets.right + options.bubbles.padding.left + 16)
140 |
141 | left = options.safeAreaInsets.left + options.bubbles.padding.right + 16
142 | }
143 |
144 | edges.append((.left, reletivePosition.minX > left))
145 |
146 | edges.append((.top, reletivePosition.minY > top))
147 |
148 | edges.append((.right, reletivePosition.maxX < right))
149 |
150 | edges.append((.bottom, reletivePosition.maxY < bottom))
151 |
152 | guard let doIHaveEnoughSpace = edges.first(where: {$0.0 == preferredPosition ?? options.defaultBubblePosition}), doIHaveEnoughSpace.1 else {
153 | return edges.first(where: {$0.0 != preferredPosition ?? options.defaultBubblePosition && $0.1})?.0 ?? options.defaultBubblePosition
154 | }
155 | return preferredPosition ?? options.defaultBubblePosition
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/Tests/TipSeeTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(TipSeeTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/TipSee.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint TipSee.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 https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.version = '1.6.3'
11 | s.name = 'TipSee'
12 | s.module_name = 'TipSee'
13 | s.summary = 'A lightweight, highly customizable tip / hint library for Swift'
14 | s.swift_version = '5.0'
15 |
16 | s.description = <<-DESC
17 | TipSee is a lightweight and highly customizable library that helps you to show beautiful tips and hints.
18 | DESC
19 |
20 | s.homepage = 'https://github.com/farshadjahanmanesh/TipSee'
21 | s.screenshots = 'https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_1.png','https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_2.png', 'https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_3.png', 'https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_4.png', 'https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_5.png','https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC_6.png','https://github.com/farshadjahanmanesh/tipC/raw/master/Example/images/TipC.gif'
22 | s.license = { :type => 'MIT', :file => 'LICENSE' }
23 | s.author = { 'farshadjahanmanesh' => 'farshadjahanmanesh@gmail.com' }
24 | s.source = { :git => 'https://github.com/farshadjahanmanesh/TipSee', :tag => s.version.to_s }
25 | s.social_media_url = 'https://twitter.com/'
26 | s.ios.deployment_target = '9.0'
27 | s.source_files = 'Sources/**/*.swift'
28 | s.frameworks = 'UIKit'
29 | end
30 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------