diff --git a/.gitignore b/.gitignore index 1e6cf1c..5073505 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,2 @@ -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control -# -# Pods/ \ No newline at end of file +## User settings +xcuserdata/ diff --git a/.jazzy.yml b/.jazzy.yml index bd26c1a..9871058 100644 --- a/.jazzy.yml +++ b/.jazzy.yml @@ -5,12 +5,12 @@ github_url: https://github.com/pronebird/UIScrollView-InfiniteScroll github_file_prefix: https://github.com/pronebird/UIScrollView-InfiniteScroll/tree/9cddbf4d89 module: UIScrollView_InfiniteScroll -module_version: 1.1.0 +module_version: 1.3.0 clean: true theme: fullwidth objc: true sdk: iphonesimulator -framework_root: Classes -umbrella_header: Classes/UIScrollView+InfiniteScroll.h +framework_root: Sources/UIScrollView_InfiniteScroll +umbrella_header: Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..d83bfb2 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,12 @@ +# general options +--swiftversion 5.5 + +# format options +--indent 4 +--maxwidth 100 +--wraparguments before-first +--wrapparameters before-first +--wrapternary before-operators +--redundanttype inferred +--ifdef no-indent +--disable initCoderUnavailable, redundantReturn, unusedArguments, trailingCommas, redundantRawValues, preferKeyPath diff --git a/CHANGES b/CHANGES index d299dfb..ba3582f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -= Unreleased += 1.3.0 (2022-08-07) * Handle scrolling when voiceOver is running (@r-dent) diff --git a/InfiniteScrollViewDemo-tvOS/Base.lproj/LaunchScreen.storyboard b/InfiniteScrollViewDemo-tvOS/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 660ba53..0000000 --- a/InfiniteScrollViewDemo-tvOS/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index 42f0a01..0b268a8 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -8,34 +8,26 @@ /* Begin PBXBuildFile section */ 5809B37B1D247F980029136A /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5809B37A1D247F980029136A /* Launch Screen.storyboard */; }; - 581FB78B2083B7E0009BF7B8 /* HackerNewsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581FB78A2083B7E0009BF7B8 /* HackerNewsResponse.swift */; }; - 581FB78D2083B850009BF7B8 /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581FB78C2083B850009BF7B8 /* Support.swift */; }; 58279BC41AF6AC5D0075D88A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BC31AF6AC5D0075D88A /* AppDelegate.swift */; }; 58279BCD1AF6AC5D0075D88A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 58279BCC1AF6AC5D0075D88A /* Images.xcassets */; }; 58279BF11AF6ADA60075D88A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 58279BF01AF6ADA60075D88A /* Main.storyboard */; }; 58279BF31AF6ADCA0075D88A /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF21AF6ADCA0075D88A /* TableViewController.swift */; }; 58279BF51AF6AEDA0075D88A /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF41AF6AEDA0075D88A /* CollectionViewController.swift */; }; 58279C011AF6C8650075D88A /* CustomInfiniteIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */; }; - 58279C081AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m in Sources */ = {isa = PBXBuildFile; fileRef = 58279C071AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m */; }; - 582A4ABF1DD8AB2D008203BA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 582A4AC11DD8AB2D008203BA /* Localizable.strings */; }; - 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */; }; + 583826A2289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */ = {isa = PBXBuildFile; fileRef = 583826A0289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m */; }; + 583826A3289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */ = {isa = PBXBuildFile; fileRef = 583826A0289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m */; }; + 583826CF289FD02500EEF6A1 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583826CE289FD02500EEF6A1 /* Models.swift */; }; + 583826D0289FD02500EEF6A1 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583826CE289FD02500EEF6A1 /* Models.swift */; }; 5869B32D25D8EB5200550415 /* Main-tvOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */; }; 5869B33025D8EB9A00550415 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF21AF6ADCA0075D88A /* TableViewController.swift */; }; - 5869B33325D8EB9E00550415 /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581FB78C2083B850009BF7B8 /* Support.swift */; }; 5869B33625D8EBA900550415 /* CustomInfiniteIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */; }; 5869B33925D8EBAB00550415 /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF41AF6AEDA0075D88A /* CollectionViewController.swift */; }; 5869B33C25D8EBAE00550415 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BC31AF6AC5D0075D88A /* AppDelegate.swift */; }; - 5869B33F25D8EC1E00550415 /* FlickrResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A7C91D1E09641A0084C93C /* FlickrResponse.swift */; }; - 5869B34025D8EC1E00550415 /* HackerNewsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581FB78A2083B7E0009BF7B8 /* HackerNewsResponse.swift */; }; - 5869B34325D8EC7700550415 /* UIScrollView+InfiniteScroll.m in Sources */ = {isa = PBXBuildFile; fileRef = 58279C071AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m */; }; 5869B34A25D8EF4D00550415 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 58279BCC1AF6AC5D0075D88A /* Images.xcassets */; }; - 58A7C91E1E09641A0084C93C /* FlickrResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A7C91D1E09641A0084C93C /* FlickrResponse.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 5809B37A1D247F980029136A /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; - 581FB78A2083B7E0009BF7B8 /* HackerNewsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackerNewsResponse.swift; sourceTree = ""; }; - 581FB78C2083B850009BF7B8 /* Support.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Support.swift; sourceTree = ""; }; 58279BBE1AF6AC5D0075D88A /* Infinite Scroll.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Infinite Scroll.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 58279BC21AF6AC5D0075D88A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58279BC31AF6AC5D0075D88A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -45,14 +37,12 @@ 58279BF21AF6ADCA0075D88A /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 58279BF41AF6AEDA0075D88A /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomInfiniteIndicator.swift; sourceTree = ""; }; - 58279C061AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+InfiniteScroll.h"; path = "Classes/UIScrollView+InfiniteScroll.h"; sourceTree = SOURCE_ROOT; }; - 58279C071AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+InfiniteScroll.m"; path = "Classes/UIScrollView+InfiniteScroll.m"; sourceTree = SOURCE_ROOT; }; - 582A4AC01DD8AB2D008203BA /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 583826A0289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+InfiniteScroll.m"; sourceTree = ""; }; + 583826A1289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+InfiniteScroll.h"; sourceTree = ""; }; + 583826CE289FD02500EEF6A1 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 5869B31425D8EB0200550415 /* InfiniteScrollViewDemo-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "InfiniteScrollViewDemo-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 5869B32025D8EB0400550415 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 5869B32225D8EB0400550415 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Main-tvOS.storyboard"; sourceTree = ""; }; - 58A7C91D1E09641A0084C93C /* FlickrResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlickrResponse.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,17 +84,15 @@ 58279BC01AF6AC5D0075D88A /* InfiniteScrollViewDemoSwift */ = { isa = PBXGroup; children = ( + 5838269F289FC76B00EEF6A1 /* UIScrollView_InfiniteScroll */, 5809B37A1D247F980029136A /* Launch Screen.storyboard */, 58279BF01AF6ADA60075D88A /* Main.storyboard */, 58279BC31AF6AC5D0075D88A /* AppDelegate.swift */, 58279BF41AF6AEDA0075D88A /* CollectionViewController.swift */, 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */, - 581FB78C2083B850009BF7B8 /* Support.swift */, 58279BF21AF6ADCA0075D88A /* TableViewController.swift */, + 583826CE289FD02500EEF6A1 /* Models.swift */, 58279BCC1AF6AC5D0075D88A /* Images.xcassets */, - 58279BEF1AF6AD2C0075D88A /* Classes */, - 582A4AC11DD8AB2D008203BA /* Localizable.strings */, - 58A7C9231E0969E80084C93C /* Models */, 58279BC11AF6AC5D0075D88A /* Supporting Files */, ); path = InfiniteScrollViewDemoSwift; @@ -119,34 +107,25 @@ name = "Supporting Files"; sourceTree = ""; }; - 58279BEF1AF6AD2C0075D88A /* Classes */ = { + 5838269F289FC76B00EEF6A1 /* UIScrollView_InfiniteScroll */ = { isa = PBXGroup; children = ( - 58279C061AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.h */, - 58279C071AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m */, + 583826A0289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m */, + 583826A1289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.h */, ); - name = Classes; - sourceTree = ""; + name = UIScrollView_InfiniteScroll; + path = Sources/UIScrollView_InfiniteScroll; + sourceTree = SOURCE_ROOT; }; 5869B31525D8EB0200550415 /* InfiniteScrollViewDemo-tvOS */ = { isa = PBXGroup; children = ( 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */, - 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */, 5869B32225D8EB0400550415 /* Info.plist */, ); path = "InfiniteScrollViewDemo-tvOS"; sourceTree = ""; }; - 58A7C9231E0969E80084C93C /* Models */ = { - isa = PBXGroup; - children = ( - 58A7C91D1E09641A0084C93C /* FlickrResponse.swift */, - 581FB78A2083B7E0009BF7B8 /* HackerNewsResponse.swift */, - ); - name = Models; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -209,10 +188,9 @@ }; buildConfigurationList = 58279BB91AF6AC5D0075D88A /* Build configuration list for PBXProject "InfiniteScrollViewDemoSwift" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -233,7 +211,6 @@ buildActionMask = 2147483647; files = ( 5809B37B1D247F980029136A /* Launch Screen.storyboard in Resources */, - 582A4ABF1DD8AB2D008203BA /* Localizable.strings in Resources */, 58279BCD1AF6AC5D0075D88A /* Images.xcassets in Resources */, 58279BF11AF6ADA60075D88A /* Main.storyboard in Resources */, ); @@ -244,7 +221,6 @@ buildActionMask = 2147483647; files = ( 5869B34A25D8EF4D00550415 /* Images.xcassets in Resources */, - 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */, 5869B32D25D8EB5200550415 /* Main-tvOS.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -256,13 +232,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 581FB78D2083B850009BF7B8 /* Support.swift in Sources */, - 58A7C91E1E09641A0084C93C /* FlickrResponse.swift in Sources */, + 583826A2289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */, 58279C011AF6C8650075D88A /* CustomInfiniteIndicator.swift in Sources */, 58279BF51AF6AEDA0075D88A /* CollectionViewController.swift in Sources */, - 581FB78B2083B7E0009BF7B8 /* HackerNewsResponse.swift in Sources */, + 583826CF289FD02500EEF6A1 /* Models.swift in Sources */, 58279BF31AF6ADCA0075D88A /* TableViewController.swift in Sources */, - 58279C081AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m in Sources */, 58279BC41AF6AC5D0075D88A /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -271,45 +245,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5869B34325D8EC7700550415 /* UIScrollView+InfiniteScroll.m in Sources */, + 583826A3289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */, 5869B33925D8EBAB00550415 /* CollectionViewController.swift in Sources */, 5869B33025D8EB9A00550415 /* TableViewController.swift in Sources */, 5869B33625D8EBA900550415 /* CustomInfiniteIndicator.swift in Sources */, - 5869B34025D8EC1E00550415 /* HackerNewsResponse.swift in Sources */, 5869B33C25D8EBAE00550415 /* AppDelegate.swift in Sources */, - 5869B33325D8EB9E00550415 /* Support.swift in Sources */, - 5869B33F25D8EC1E00550415 /* FlickrResponse.swift in Sources */, + 583826D0289FD02500EEF6A1 /* Models.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - 582A4AC11DD8AB2D008203BA /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - 582A4AC01DD8AB2D008203BA /* en */, - ); - name = Localizable.strings; - sourceTree = ""; - }; - 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 5869B32025D8EB0400550415 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 58279BDD1AF6AC5D0075D88A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; @@ -336,7 +287,6 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -352,10 +302,10 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -364,8 +314,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; @@ -392,7 +340,6 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -401,9 +348,9 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -421,7 +368,6 @@ PRODUCT_NAME = "Infinite Scroll"; SWIFT_OBJC_BRIDGING_HEADER = "InfiniteScrollViewDemoSwift/InfiniteScrollViewDemoSwift-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -437,7 +383,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.github.pronebird.InfiniteScrollViewDemo; PRODUCT_NAME = "Infinite Scroll"; SWIFT_OBJC_BRIDGING_HEADER = "InfiniteScrollViewDemoSwift/InfiniteScrollViewDemoSwift-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -449,7 +394,6 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -457,17 +401,13 @@ CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ZXGXFAG6WF; - GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "InfiniteScrollViewDemo-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.github.pronebird.InfiniteScrollViewDemo-tvOS.InfiniteScrollViewDemo-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "InfiniteScrollViewDemoSwift/InfiniteScrollViewDemoSwift-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; @@ -480,22 +420,18 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ZXGXFAG6WF; - GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "InfiniteScrollViewDemo-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.github.pronebird.InfiniteScrollViewDemo-tvOS.InfiniteScrollViewDemo-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OBJC_BRIDGING_HEADER = "InfiniteScrollViewDemoSwift/InfiniteScrollViewDemoSwift-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/InfiniteScrollViewDemoSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/InfiniteScrollViewDemoSwift/AppDelegate.swift b/InfiniteScrollViewDemoSwift/AppDelegate.swift index 237eae1..3741018 100644 --- a/InfiniteScrollViewDemoSwift/AppDelegate.swift +++ b/InfiniteScrollViewDemoSwift/AppDelegate.swift @@ -10,11 +10,12 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { - var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { return true } } - diff --git a/InfiniteScrollViewDemoSwift/CollectionViewController.swift b/InfiniteScrollViewDemoSwift/CollectionViewController.swift index 996b16f..e462772 100644 --- a/InfiniteScrollViewDemoSwift/CollectionViewController.swift +++ b/InfiniteScrollViewDemoSwift/CollectionViewController.swift @@ -12,17 +12,16 @@ import SafariServices #endif class CollectionViewController: UICollectionViewController { - fileprivate let downloadQueue = DispatchQueue(label: "Photo cache", qos: .background) - + fileprivate var items = [FlickrItem]() fileprivate var cache = NSCache() - + // MARK: - Lifecycle - + override func viewDidLoad() { super.viewDidLoad() - + // Set custom indicator let indicatorRect: CGRect #if os(tvOS) @@ -31,43 +30,49 @@ class CollectionViewController: UICollectionViewController { indicatorRect = CGRect(x: 0, y: 0, width: 24, height: 24) #endif collectionView?.infiniteScrollIndicatorView = CustomInfiniteIndicator(frame: indicatorRect) - + // Set custom indicator margin collectionView?.infiniteScrollIndicatorMargin = 40 - + // Add infinite scroll handler - collectionView?.addInfiniteScroll { [weak self] (scrollView) -> Void in - self?.performFetch({ + collectionView?.addInfiniteScroll { [weak self] scrollView in + self?.performFetch { scrollView.finishInfiniteScroll() - }) + } } - + // load initial data collectionView?.beginInfiniteScroll(true) } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + + override func viewWillTransition( + to size: CGSize, + with coordinator: UIViewControllerTransitionCoordinator + ) { super.viewWillTransition(to: size, with: coordinator) - + collectionViewLayout.invalidateLayout() } - - fileprivate func downloadPhoto(_ url: URL, completion: @escaping (_ url: URL, _ image: UIImage) -> Void) { - downloadQueue.async(execute: { () -> Void in + + fileprivate func downloadPhoto( + _ url: URL, + completion: @escaping (URL, UIImage) -> Void + ) { + downloadQueue.async { if let image = self.cache.object(forKey: url as NSURL) { DispatchQueue.main.async { completion(url, image) } - return } - + do { let data = try Data(contentsOf: url) - + if let image = UIImage(data: data) { + self.cache.setObject(image, forKey: url as NSURL) + DispatchQueue.main.async { - self.cache.setObject(image, forKey: url as NSURL) completion(url, image) } } else { @@ -76,92 +81,119 @@ class CollectionViewController: UICollectionViewController { } catch { print("Could not load URL: \(url): \(error)") } - }) + } } - + fileprivate func performFetch(_ completionHandler: (() -> Void)?) { - fetchData { (result) in - switch result { - case .ok(let response): + fetchData { response, error in + if let error = error { + self.showAlertWithError(error) + } else if let response = response { let newItems = response.items - + // create new index paths let photoCount = self.items.count let (start, end) = (photoCount, newItems.count + photoCount) - let indexPaths = (start.. Void in + self.collectionView?.performBatchUpdates({ () in self.collectionView?.insertItems(at: indexPaths) - }, completion: { (finished) -> Void in + }, completion: { _ in completionHandler?() - }); - - case .error(let error): - self.showAlertWithError(error) + }) } } } - + fileprivate func showAlertWithError(_ error: Error) { - let alert = UIAlertController(title: NSLocalizedString("collectionView.errorAlert.title", comment: ""), - message: error.localizedDescription, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.dismiss", comment: ""), - style: .cancel, - handler: nil)) - - alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.retry", comment: ""), - style: .default, - handler: { _ in self.performFetch(nil) })) - - self.present(alert, animated: true, completion: nil) - } + let alertController = UIAlertController( + title: NSLocalizedString( + "collectionView.errorAlert.title", + value: "Failed to fetch data", + comment: "" + ), + message: error.localizedDescription, + preferredStyle: .alert + ) + + alertController.addAction(UIAlertAction( + title: NSLocalizedString( + "collectionView.errorAlert.dismiss", + value: "Dismiss", + comment: "" + ), + style: .cancel, + handler: nil + )) + alertController.addAction(UIAlertAction( + title: NSLocalizedString( + "collectionView.errorAlert.retry", + value: "Retry", + comment: "" + ), + style: .default, + handler: { _ in self.performFetch(nil) } + )) + + present(alertController, animated: true, completion: nil) + } } // MARK: - Actions extension CollectionViewController { - @IBAction func handleRefresh() { collectionView?.beginInfiniteScroll(true) } - } // MARK: - UICollectionViewDelegateFlowLayout extension CollectionViewController: UICollectionViewDelegateFlowLayout { - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let collectionWidth = collectionView.bounds.width; + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath + ) -> CGSize { + let collectionWidth = collectionView.bounds.width let itemWidth: CGFloat - switch self.traitCollection.userInterfaceIdiom { + switch traitCollection.userInterfaceIdiom { case .pad: itemWidth = collectionWidth / 4 - 1 case .tv: - let spacing = self.collectionView(collectionView, layout: collectionViewLayout, minimumInteritemSpacingForSectionAt: indexPath.section) + let spacing = self.collectionView( + collectionView, + layout: collectionViewLayout, + minimumInteritemSpacingForSectionAt: indexPath.section + ) itemWidth = collectionWidth / 8 - spacing default: itemWidth = collectionWidth / 3 - 1 - } - - return CGSize(width: itemWidth, height: itemWidth); + + return CGSize(width: itemWidth, height: itemWidth) } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumInteritemSpacingForSectionAt section: Int + ) -> CGFloat { return 1 } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumLineSpacingForSectionAt section: Int + ) -> CGFloat { return 1 } } @@ -169,25 +201,33 @@ extension CollectionViewController: UICollectionViewDelegateFlowLayout { // MARK: - UICollectionViewDataSource extension CollectionViewController { - - override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + override func collectionView( + _ collectionView: UICollectionView, + numberOfItemsInSection section: Int + ) -> Int { return items.count } - - override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + override func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { let item = items[indexPath.item] let mediaUrl = item.mediumMediaUrl! let image = cache.object(forKey: mediaUrl as NSURL) - - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell + + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: "PhotoCell", + for: indexPath + ) as! PhotoCell cell.imageView.image = image - + if image == nil { - downloadPhoto(mediaUrl, completion: { (url, image) -> Void in + downloadPhoto(mediaUrl, completion: { url, image in collectionView.reloadItems(at: [indexPath]) }) } - + return cell } } @@ -195,42 +235,41 @@ extension CollectionViewController { // MARK: - UICollectionViewDelegate extension CollectionViewController { - - override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + override func collectionView( + _ collectionView: UICollectionView, + didSelectItemAt indexPath: IndexPath + ) { let model = items[indexPath.row] #if !os(tvOS) let safariController = SFSafariViewController(url: model.link) safariController.delegate = self - - let safariNavigationController = UINavigationController(rootViewController: safariController) + + let safariNavigationController = + UINavigationController(rootViewController: safariController) safariNavigationController.setNavigationBarHidden(true, animated: false) - + present(safariNavigationController, animated: true) #endif - + collectionView.deselectItem(at: indexPath, animated: true) } - } // MARK: - SFSafariViewControllerDelegate #if !os(tvOS) extension CollectionViewController: SFSafariViewControllerDelegate { - func safariViewControllerDidFinish(_ controller: SFSafariViewController) { controller.dismiss(animated: true) } - } #endif // MARK: - Cells class PhotoCell: UICollectionViewCell { - - @IBOutlet weak var imageView: UIImageView! + @IBOutlet var imageView: UIImageView! override func awakeFromNib() { if #available(iOS 13.0, *) { @@ -243,29 +282,43 @@ class PhotoCell: UICollectionViewCell { imageView.backgroundColor = UIColor(white: 0.95, alpha: 1) } } - } // MARK: - API -extension CollectionViewController { - typealias FetchResult = Result - - fileprivate func fetchData(handler: @escaping ((FetchResult) -> Void)) { - let requestUrl = URL(string: "https://api.flickr.com/services/feeds/photos_public.gne?nojsoncallback=1&format=json")! - - let task = URLSession.shared.dataTask(with: requestUrl, completionHandler: { (data, _, networkError) in - DispatchQueue.main.async { - handler(handleFetchResponse(data: data, networkError: networkError)) +private extension CollectionViewController { + func fetchData(completion: @escaping ((FlickrResponse?, Error?) -> Void)) { + let requestURL = + URL( + string: "https://api.flickr.com/services/feeds/photos_public.gne?nojsoncallback=1&format=json" + )! + + let task = URLSession.shared.dataTask( + with: requestURL, + completionHandler: { data, _, error in + DispatchQueue.main.async { + if let error = error { + completion(nil, error) + return + } + + do { + let response = try JSONDecoder() + .decode(FlickrResponse.self, from: data ?? Data()) + + completion(response, nil) + } catch { + completion(nil, error) + } + } } - }) - + ) + // I run task.resume() with delay because my network is too fast let delay = items.count == 0 ? 0 : 5 - - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(delay), execute: { + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(delay)) { task.resume() - }) + } } - } diff --git a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift index a4c7d92..c955cab 100644 --- a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift +++ b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift @@ -11,13 +11,13 @@ import UIKit private let rotationAnimationKey = "rotation" class CustomInfiniteIndicator: UIView { - var thickness: CGFloat = 2 var outerColor: UIColor? { didSet { updateColors() } } + var innerColor: UIColor? { didSet { updateColors() @@ -27,33 +27,31 @@ class CustomInfiniteIndicator: UIView { private var animating = false private let innerCircle = CAShapeLayer() private let outerCircle = CAShapeLayer() - private var startTime = CFTimeInterval(0) - private var stopTime = CFTimeInterval(0) // MARK: - Public - + override init(frame: CGRect) { super.init(frame: frame) commonInit() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } - + deinit { unregisterFromAppStateNotifications() } - + override func layoutSublayers(of layer: CALayer) { super.layoutSublayers(of: layer) setupBezierPaths() } - + override func didMoveToWindow() { super.didMoveToWindow() - + if let _ = window { restartAnimationIfNeeded() } @@ -66,6 +64,8 @@ class CustomInfiniteIndicator: UIView { } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if #available(iOS 13.0, tvOS 13.0, *) { if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { updateColors() @@ -90,12 +90,12 @@ class CustomInfiniteIndicator: UIView { isHidden = true removeAnimation() } - + // MARK: - Private - + private func commonInit() { registerForAppStateNotifications() - + isHidden = true backgroundColor = UIColor.clear @@ -106,88 +106,89 @@ class CustomInfiniteIndicator: UIView { innerCircle.lineWidth = thickness updateColors() - + layer.addSublayer(outerCircle) layer.addSublayer(innerCircle) } private func updateColors() { - let outerColor = self.outerColor ?? self.defaultOuterColor() - let innerColor = self.innerColor ?? self.tintColor + let outerColor = outerColor ?? defaultOuterColor() + let innerColor = innerColor ?? tintColor outerCircle.strokeColor = outerColor.cgColor innerCircle.strokeColor = innerColor?.cgColor } - + private func addAnimation() { let anim = animation() - anim.timeOffset = stopTime - startTime - + + anim.timeOffset = layer.convertTime(CACurrentMediaTime(), from: nil) layer.add(anim, forKey: rotationAnimationKey) - - startTime = layer.convertTime(CACurrentMediaTime(), from: nil) } - + private func removeAnimation() { layer.removeAnimation(forKey: rotationAnimationKey) - - stopTime = layer.convertTime(CACurrentMediaTime(), from: nil) } - + @objc func restartAnimationIfNeeded() { let anim = layer.animation(forKey: rotationAnimationKey) - - if animating && anim == nil { + + if animating, anim == nil { removeAnimation() addAnimation() } } - + private func registerForAppStateNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(CustomInfiniteIndicator.restartAnimationIfNeeded), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(CustomInfiniteIndicator.restartAnimationIfNeeded), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) } - + private func unregisterFromAppStateNotifications() { NotificationCenter.default.removeObserver(self) } - + private func animation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "transform.rotation") animation.toValue = NSNumber(value: Double.pi * 2) animation.duration = 1 animation.repeatCount = Float.infinity animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - + return animation } - + private func setupBezierPaths() { let center = CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5) let radius = bounds.size.width * 0.5 - thickness - let ringPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(0), endAngle: CGFloat.pi * 2, clockwise: true) - let quarterRingPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: -CGFloat.pi / 4, endAngle: CGFloat.pi / 2 - CGFloat.pi / 4, clockwise: true) - + let ringPath = UIBezierPath( + arcCenter: center, + radius: radius, + startAngle: CGFloat(0), + endAngle: CGFloat.pi * 2, + clockwise: true + ) + let quarterRingPath = UIBezierPath( + arcCenter: center, + radius: radius, + startAngle: -CGFloat.pi / 4, + endAngle: CGFloat.pi / 2 - CGFloat.pi / 4, + clockwise: true + ) + outerCircle.path = ringPath.cgPath innerCircle.path = quarterRingPath.cgPath } private func defaultOuterColor() -> UIColor { - let defaultLightColor = UIColor.gray.withAlphaComponent(0.2) - if #available(iOS 13.0, tvOS 13, *) { - return UIColor { (traitCollection) -> UIColor in - switch traitCollection.userInterfaceStyle { - case .light, .unspecified: - return defaultLightColor - case .dark: - return UIColor.white.withAlphaComponent(0.5) - @unknown default: - fatalError() - } - } + return .systemGray4 } else { - return defaultLightColor + return .gray.withAlphaComponent(0.2) } } - } diff --git a/InfiniteScrollViewDemoSwift/FlickrResponse.swift b/InfiniteScrollViewDemoSwift/FlickrResponse.swift deleted file mode 100644 index f773132..0000000 --- a/InfiniteScrollViewDemoSwift/FlickrResponse.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// FlickrModel.swift -// InfiniteScrollViewDemoSwift -// -// Created by pronebird on 12/20/16. -// Copyright © 2016 pronebird. All rights reserved. -// - -import Foundation - -struct FlickrItem: Decodable { - let link: URL - let media: [String: URL] - var mediumMediaUrl: URL? { - return media["m"] - } -} - -struct FlickrResponse: Decodable { - let items: [FlickrItem] -} diff --git a/InfiniteScrollViewDemoSwift/HackerNewsResponse.swift b/InfiniteScrollViewDemoSwift/HackerNewsResponse.swift deleted file mode 100644 index 44ddbf5..0000000 --- a/InfiniteScrollViewDemoSwift/HackerNewsResponse.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HackerNewsStory.swift -// InfiniteScrollViewDemoSwift -// -// Created by pronebird on 4/15/18. -// Copyright © 2018 pronebird. All rights reserved. -// - -import Foundation - -struct HackerNewsStory: Decodable { - let objectID: String - let title: String - let author: String - let url: URL? - var postUrl: URL { - return URL(string: "https://news.ycombinator.com/item?id=\(self.objectID)")! - } -} - -struct HackerNewsResponse: Decodable { - let hits: [HackerNewsStory] - let nbPages: Int -} diff --git a/InfiniteScrollViewDemoSwift/Info.plist b/InfiniteScrollViewDemoSwift/Info.plist index 7ef1223..e6a1b5b 100644 --- a/InfiniteScrollViewDemoSwift/Info.plist +++ b/InfiniteScrollViewDemoSwift/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -26,6 +26,8 @@ NSAllowsArbitraryLoads + NSAllowsArbitraryLoadsInWebContent + UILaunchStoryboardName Launch Screen diff --git a/InfiniteScrollViewDemoSwift/Models.swift b/InfiniteScrollViewDemoSwift/Models.swift new file mode 100644 index 0000000..7a4e622 --- /dev/null +++ b/InfiniteScrollViewDemoSwift/Models.swift @@ -0,0 +1,36 @@ +// +// Models.swift +// InfiniteScrollViewDemoSwift +// +// Created by pronebird on 7/8/22. +// Copyright © 2022 pronebird. All rights reserved. +// + +import Foundation + +struct FlickrItem: Decodable { + let link: URL + let media: [String: URL] + var mediumMediaUrl: URL? { + media["m"] + } +} + +struct FlickrResponse: Decodable { + let items: [FlickrItem] +} + +struct HackerNewsStory: Decodable { + let objectID: String + let title: String + let author: String + let url: URL? + var postUrl: URL { + URL(string: "https://news.ycombinator.com/item?id=\(objectID)")! + } +} + +struct HackerNewsResponse: Decodable { + let hits: [HackerNewsStory] + let nbPages: Int +} diff --git a/InfiniteScrollViewDemoSwift/Support.swift b/InfiniteScrollViewDemoSwift/Support.swift deleted file mode 100644 index 02d5d3e..0000000 --- a/InfiniteScrollViewDemoSwift/Support.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Support.swift -// InfiniteScrollViewDemoSwift -// -// Created by pronebird on 4/15/18. -// Copyright © 2018 pronebird. All rights reserved. -// - -import Foundation - -enum Result { - case ok(T) - case error(E) -} - -enum FetchError: Error { - case load(Error) - case noData - case deserialization(Error) -} - -extension FetchError: LocalizedError { - var errorDescription: String? { - switch self { - case .load(_): - return NSLocalizedString("fetchError.load", comment: "") - case .deserialization(_): - return NSLocalizedString("fetchError.deserialization", comment: "") - case .noData: - return NSLocalizedString("fetchError.noData", comment: "") - } - } -} - -func handleFetchResponse(data: Data?, networkError: Error?) -> Result { - if let networkError = networkError { - return .error(FetchError.load(networkError)) - } - - guard let data = data else { - return .error(FetchError.noData) - } - - do { - let response = try JSONDecoder().decode(T.self, from: data) - return .ok(response) - } catch { - return .error(FetchError.deserialization(error)) - } -} diff --git a/InfiniteScrollViewDemoSwift/TableViewController.swift b/InfiniteScrollViewDemoSwift/TableViewController.swift index 1371729..31000ea 100644 --- a/InfiniteScrollViewDemoSwift/TableViewController.swift +++ b/InfiniteScrollViewDemoSwift/TableViewController.swift @@ -14,21 +14,20 @@ import SafariServices private let useAutosizingCells = true class TableViewController: UITableViewController { - fileprivate var currentPage = 0 fileprivate var numPages = 0 fileprivate var stories = [HackerNewsStory]() - + // MARK: - Lifecycle - + override func viewDidLoad() { super.viewDidLoad() - - if useAutosizingCells && tableView.responds(to: #selector(getter: UIView.layoutMargins)) { + + if useAutosizingCells, tableView.responds(to: #selector(getter: UIView.layoutMargins)) { tableView.estimatedRowHeight = 88 tableView.rowHeight = UITableView.automaticDimension } - + // Set custom indicator let indicatorRect: CGRect #if os(tvOS) @@ -37,176 +36,194 @@ class TableViewController: UITableViewController { indicatorRect = CGRect(x: 0, y: 0, width: 24, height: 24) #endif tableView.infiniteScrollIndicatorView = CustomInfiniteIndicator(frame: indicatorRect) - + // Set custom indicator margin tableView.infiniteScrollIndicatorMargin = 40 - + // Set custom trigger offset tableView.infiniteScrollTriggerOffset = 500 - + // Add infinite scroll handler - tableView.addInfiniteScroll { [weak self] (tableView) -> Void in + tableView.addInfiniteScroll { [weak self] tableView in self?.performFetch { tableView.finishInfiniteScroll() } } - + // Uncomment this to provide conditionally prevent the infinite scroll from triggering /* - tableView.setShouldShowInfiniteScrollHandler { [weak self] (tableView) -> Bool in - guard let self = self else { return false } + tableView.setShouldShowInfiniteScrollHandler { [weak self] (tableView) -> Bool in + guard let self = self else { return false } + + // Only show up to 5 pages then prevent the infinite scroll + return self.currentPage < 5 + } + */ - // Only show up to 5 pages then prevent the infinite scroll - return self.currentPage < 5 - } - */ - // load initial data tableView.beginInfiniteScroll(true) } - + fileprivate func performFetch(_ completionHandler: (() -> Void)?) { - fetchData { (result) in - defer { completionHandler?() } - - switch result { - case .ok(let response): + fetchData { response, error in + if let error = error { + self.showAlertWithError(error) + } else if let response = response { // create new index paths let storyCount = self.stories.count let (start, end) = (storyCount, response.hits.count + storyCount) - let indexPaths = (start.. Int { return stories.count } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { let story = stories[indexPath.row] - + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = story.title cell.detailTextLabel?.text = story.author - - if useAutosizingCells && tableView.responds(to: #selector(getter: UIView.layoutMargins)) { + + if useAutosizingCells, tableView.responds(to: #selector(getter: UIView.layoutMargins)) { cell.textLabel?.numberOfLines = 0 cell.detailTextLabel?.numberOfLines = 0 } - + return cell } - } // MARK: - SFSafariViewControllerDelegate + #if !os(tvOS) extension TableViewController: SFSafariViewControllerDelegate { - func safariViewControllerDidFinish(_ controller: SFSafariViewController) { controller.dismiss(animated: true) } - } #endif // MARK: - API -extension TableViewController { - typealias FetchResult = Result - - fileprivate func makeRequest(numHits: Int, page: Int) -> URLRequest { - let url = URL(string: "https://hn.algolia.com/api/v1/search_by_date?tags=story&hitsPerPage=\(numHits)&page=\(page)")! +private extension TableViewController { + func makeRequest(numHits: Int, page: Int) -> URLRequest { + let url = + URL( + string: "https://hn.algolia.com/api/v1/search_by_date?tags=story&hitsPerPage=\(numHits)&page=\(page)" + )! return URLRequest(url: url) } - fileprivate func fetchData(handler: @escaping ((FetchResult) -> Void)) { + func fetchData(completion: @escaping ((HackerNewsResponse?, Error?) -> Void)) { let hits = Int(tableView.bounds.height) / 44 let request = makeRequest(numHits: hits, page: currentPage) - - let task = URLSession.shared.dataTask(with: request, completionHandler: { - (data, _, networkError) -> Void in + + let task = URLSession.shared.dataTask(with: request, completionHandler: { data, _, error in DispatchQueue.main.async { - handler(handleFetchResponse(data: data, networkError: networkError)) + if let error = error { + completion(nil, error) + return + } + + do { + let response = try JSONDecoder() + .decode(HackerNewsResponse.self, from: data ?? Data()) + + completion(response, nil) + } catch { + completion(nil, error) + } } }) - + // I run task.resume() with delay because my network is too fast let delay = (stories.count == 0 ? 0 : 5) - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(delay), execute: { + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(delay)) { task.resume() - }) + } } - } diff --git a/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings b/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings deleted file mode 100644 index be005ee..0000000 --- a/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings +++ /dev/null @@ -1,27 +0,0 @@ -/* No comment provided by engineer. */ -"collectionView.errorAlert.dismiss" = "Dismiss"; - -/* No comment provided by engineer. */ -"collectionView.errorAlert.retry" = "Retry"; - -/* No comment provided by engineer. */ -"collectionView.errorAlert.title" = "Failed to fetch data"; - -/* No comment provided by engineer. */ -"fetchError.deserialization" = "Couldn't parse response"; - -/* No comment provided by engineer. */ -"fetchError.load" = "Couldn't load data"; - -/* No comment provided by engineer. */ -"fetchError.noData" = "Couldn't receive data"; - -/* No comment provided by engineer. */ -"tableView.errorAlert.dismiss" = "Dismiss"; - -/* No comment provided by engineer. */ -"tableView.errorAlert.retry" = "Retry"; - -/* No comment provided by engineer. */ -"tableView.errorAlert.title" = "Failed to fetch data"; - diff --git a/LICENSE b/LICENSE index 637cafc..69be652 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2015 Andrei Mihailov +Copyright (c) 2013-2022 Andrei Mihailov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Package.swift b/Package.swift index 27ba736..f3a9e2f 100644 --- a/Package.swift +++ b/Package.swift @@ -6,21 +6,20 @@ import PackageDescription let package = Package( name: "UIScrollView_InfiniteScroll", platforms: [ - .iOS(.v9) + .iOS(.v9), + .tvOS(.v9), ], products: [ .library( name: "UIScrollView_InfiniteScroll", targets: ["UIScrollView_InfiniteScroll"] - ) + ), ], dependencies: [], targets: [ .target( name: "UIScrollView_InfiniteScroll", - dependencies: [], - path: "Classes", - publicHeadersPath: "" + publicHeadersPath: "." ), ] ) diff --git a/README.md b/README.md index 6ec5530..b62845c 100644 --- a/README.md +++ b/README.md @@ -16,73 +16,119 @@ Infinite scroll implementation as a category for UIScrollView. -\* The content used in demo app is publicly available and provided by hn.algolia.com and Flickr. Both can be inappropriate. +\* The content used in demo app is publicly available and provided by hn.algolia.com and Flickr. +Both can be inappropriate. ### Swizzling -Be aware that this category [swizzles](http://nshipster.com/method-swizzling/) `setContentOffset` and `setContentSize` on `UIScrollView`. +Be aware that this category [swizzles](http://nshipster.com/method-swizzling/) `setContentOffset` +and `setContentSize` on `UIScrollView`. -### CocoaPods +### Swift Package Manager -Just add the following line in your Podfile: +Add new package using github repo URL: -```ruby -pod 'UIScrollView-InfiniteScroll', '~> 1.2.0' +``` +https://github.com/pronebird/UIScrollView-InfiniteScroll ``` -### Carthage +Then import module in the source code: -Just add the following line in your Cartfile: +```swift +import UIScrollView_InfiniteScroll +``` + +### CocoaPods + +Add the following line in your Podfile: ```ruby -github "pronebird/UIScrollView-InfiniteScroll" ~> 1.2.0 +pod 'UIScrollView-InfiniteScroll', '~> 1.3.0' ``` -### Examples +#### Objective-C -This component comes with example app written in Swift and Objective-C. +```objc +#import +``` -If you use CocoaPods you can try it by running: +or if using modules: -```bash -pod try UIScrollView-InfiniteScroll +```objc +@import UIScrollView_InfiniteScroll; ``` -### Documentation +#### Swift -http://pronebird.github.io/UIScrollView-InfiniteScroll/ +Add the following line in your bridging header file: + +```objc +#import +``` -### Before using module +### Carthage -#### Objective-C +Add the following line in your Cartfile: + +```ruby +github "pronebird/UIScrollView-InfiniteScroll" ~> 1.3.0 +``` -Import header file in Objective-C: +#### Objective-C ```objc #import ``` +or if using modules: + +```objc +@import UIScrollView_InfiniteScroll; +``` + #### Swift -Add the following line in your bridging header file: +```swift +import UIScrollView_InfiniteScroll +``` -```objc -#import +### Examples + +This component comes with example app written in Swift. + +If you use CocoaPods you can try it by running: + +```bash +pod try UIScrollView-InfiniteScroll ``` +### Documentation + +http://pronebird.github.io/UIScrollView-InfiniteScroll/ + ### Basics -In order to enable infinite scroll you have to provide a handler block using `addInfiniteScrollWithHandler`. The block you provide is executed each time infinite scroll component detects that more data needs to be provided. +In order to enable infinite scroll you have to provide a handler block using +`addInfiniteScrollWithHandler`. The block you provide is executed each time infinite scroll +component detects that more data needs to be provided. -The purpose of the handler block is to perform asynchronous task, typically networking or database fetch, and update your scroll view or scroll view subclass. +The purpose of the handler block is to perform asynchronous task, typically networking or database +fetch, and update your scroll view or scroll view subclass. -The block itself is called on main queue, therefore make sure you move any long-running tasks to background queue. Once you receive new data, update table view by adding new rows and sections, then call `finishInfiniteScroll` to complete infinite scroll animations and reset the state of infinite scroll components. +The block itself is called on main queue, therefore make sure you move any long-running tasks to +background queue. Once you receive new data, update table view by adding new rows and sections, +then call `finishInfiniteScroll` to complete infinite scroll animations and reset the state of +infinite scroll components. `viewDidLoad` is a good place to install handler block. -Make sure that any interactions with UIKit or methods provided by Infinite Scroll happen on main queue. Use `dispatch_async(dispatch_get_main_queue, { ... })` in Objective-C or `DispatchQueue.main.async { ... }` in Swift to run UI related calls on main queue. +Make sure that any interactions with UIKit or methods provided by Infinite Scroll happen on main +queue. Use `dispatch_async(dispatch_get_main_queue, { ... })` in Objective-C or +`DispatchQueue.main.async { ... }` in Swift to run UI related calls on main queue. -Many people make mistake by using external reference to table view or collection view within the handler block. Don't do this. This creates a circular retention. Instead use the instance of scroll view or scroll view subclass passed as first argument to handler block. +Many people make mistake by using external reference to table view or collection view within the +handler block. Don't do this. This creates a circular retention. Instead use the instance of scroll +view or scroll view subclass passed as first argument to handler block. #### Objective-C @@ -109,7 +155,8 @@ tableView.addInfiniteScroll { (tableView) -> Void in ### Collection view quirks -`UICollectionView.reloadData` causes contentOffset to reset. Instead use `UICollectionView.performBatchUpdates` when possible. +`UICollectionView.reloadData` causes contentOffset to reset. Instead use +`UICollectionView.performBatchUpdates` when possible. #### Objective-C @@ -139,9 +186,13 @@ collectionView.addInfiniteScroll { (collectionView) -> Void in ### Start infinite scroll programmatically -You can reuse infinite scroll flow to load initial data or fetch more using `beginInfiniteScroll(forceScroll)`. `viewDidLoad` is a good place for loading initial data, however absolutely up to you to decide. +You can reuse infinite scroll flow to load initial data or fetch more using +`beginInfiniteScroll(forceScroll)`. `viewDidLoad` is a good place for loading initial data, +however absolutely up to you to decide that. -When `forceScroll` parameter is positive, Infinite Scroll component will attempt to scroll down to reveal indicator view. Keep in mind that scrolling will not happen if user is interacting with scroll view. +When `forceScroll` parameter is `true`, Infinite Scroll component will attempt to scroll down to +reveal indicator view. Keep in mind that scrolling will not happen if user is interacting with +scroll view. #### Objective-C @@ -157,7 +208,8 @@ tableView.beginInfiniteScroll(true) ### Prevent infinite scroll -Sometimes you need to prevent the infinite scroll from continuing. For example, if your search API has no more results, it does not make sense to keep making the requests or to show the spinner. +Sometimes you need to prevent the infinite scroll from continuing. For example, if your search API +has no more results, it does not make sense to keep making the requests or to show the spinner. #### Objective-C @@ -171,7 +223,8 @@ Sometimes you need to prevent the infinite scroll from continuing. For example, #### Swift ```swift -// Provide a block to be called right before a infinite scroll event is triggered. Return YES to allow or NO to prevent it from triggering. +// Provide a block to be called right before a infinite scroll event is triggered. +// Return YES to allow or NO to prevent it from triggering. tableView.setShouldShowInfiniteScrollHandler { _ -> Bool in // Only show up to 5 pages then prevent the infinite scroll return currentPage < 5 @@ -180,9 +233,13 @@ tableView.setShouldShowInfiniteScrollHandler { _ -> Bool in ### Seamlessly preload content -Ideally you want your content to flow seamlessly without ever showing a spinner. Infinite scroll offers an option to specify offset in points that will be used to start preloader before user reaches the bottom of scroll view. +Ideally you want your content to flow seamlessly without ever showing a spinner. Infinite scroll +offers an option to specify offset in points that will be used to start preloader before user +reaches the bottom of scroll view. -The proper balance between the number of results you load each time and large enough offset should give your users a decent experience. Most likely you will have to come up with your own formula for the combination of those based on kind of content and device dimensions. +The proper balance between the number of results you load each time and large enough offset should +give your users a decent experience. Most likely you will have to come up with your own formula for +the combination of those based on kind of content and device dimensions. ```objc // Preload more data 500pt before reaching the bottom of scroll view. @@ -195,16 +252,9 @@ You can use custom indicator instead of default `UIActivityIndicatorView`. Custom indicator must be a subclass of `UIView` and implement the following methods: -```objc -- (void)startAnimating; -- (void)stopAnimating; -``` - -#### Objective-C - -```objc -CustomInfiniteIndicator *infiniteIndicator = [[CustomInfiniteIndicator alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; -self.tableView.infiniteScrollIndicatorView = indicator; +```swift +@objc func startAnimating() +@objc func stopAnimating() ``` #### Swift @@ -216,11 +266,11 @@ tableView.infiniteScrollIndicatorView = CustomInfiniteIndicator(frame: frame) Please see example implementation of custom indicator view: -* Objective-C: [CustomInfiniteIndicator.m](https://github.com/pronebird/UIScrollView-InfiniteScroll/blob/master/InfiniteScrollViewDemo/CustomInfiniteIndicator.m) - * Swift: [CustomInfiniteIndicator.swift](https://github.com/pronebird/UIScrollView-InfiniteScroll/blob/master/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift) -At the moment InfiniteScroll uses indicator's frame directly so make sure you size custom indicator view beforehand. Such views as `UIImageView` or `UIActivityIndicatorView` will automatically resize themselves so no need to setup frame for them. +At the moment InfiniteScroll uses indicator's frame directly so make sure you size custom indicator +view beforehand. Such views as `UIImageView` or `UIActivityIndicatorView` will automatically resize +themselves so no need to setup frame for them. ### Contributors diff --git a/Classes/UIScrollView+InfiniteScroll.h b/Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h similarity index 98% rename from Classes/UIScrollView+InfiniteScroll.h rename to Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h index edd17de..85731be 100644 --- a/Classes/UIScrollView+InfiniteScroll.h +++ b/Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h @@ -4,7 +4,7 @@ // UIScrollView infinite scroll category // // Created by Andrej Mihajlov on 9/4/13. -// Copyright (c) 2013-2015 Andrej Mihajlov. All rights reserved. +// Copyright (c) 2013-2022 Andrej Mihajlov. All rights reserved. // #import diff --git a/Classes/UIScrollView+InfiniteScroll.m b/Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.m similarity index 99% rename from Classes/UIScrollView+InfiniteScroll.m rename to Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.m index 4a9f84d..68d1603 100644 --- a/Classes/UIScrollView+InfiniteScroll.m +++ b/Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.m @@ -4,7 +4,7 @@ // UIScrollView infinite scroll category // // Created by Andrej Mihajlov on 9/4/13. -// Copyright (c) 2013-2015 Andrej Mihajlov. All rights reserved. +// Copyright (c) 2013-2022 Andrej Mihajlov. All rights reserved. // #import "UIScrollView+InfiniteScroll.h" diff --git a/UIScrollView-InfiniteScroll.podspec b/UIScrollView-InfiniteScroll.podspec index 62abb87..6f94fc0 100644 --- a/UIScrollView-InfiniteScroll.podspec +++ b/UIScrollView-InfiniteScroll.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'UIScrollView-InfiniteScroll' - s.version = '1.2.0' + s.version = '1.3.0' s.license = 'MIT' s.summary = 'UIScrollView infinite scroll category.' s.homepage = 'https://github.com/pronebird/UIScrollView-InfiniteScroll' @@ -11,7 +11,7 @@ Pod::Spec.new do |s| :git => 'https://github.com/pronebird/UIScrollView-InfiniteScroll.git', :tag => s.version.to_s } - s.source_files = 'Classes/*.{h,m}' + s.source_files = 'Sources/UIScrollView_InfiniteScroll/*.{h,m}' s.requires_arc = true s.ios.deployment_target = '9.0' end diff --git a/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj b/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj index a708e56..925f53d 100644 --- a/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj +++ b/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj @@ -8,16 +8,16 @@ /* Begin PBXBuildFile section */ 39FC9E151C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll.h in Headers */ = {isa = PBXBuildFile; fileRef = 39FC9E141C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 39FC9E1E1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.h in Headers */ = {isa = PBXBuildFile; fileRef = 39FC9E1C1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 39FC9E1F1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.m in Sources */ = {isa = PBXBuildFile; fileRef = 39FC9E1D1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.m */; }; + 58382696289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.h in Headers */ = {isa = PBXBuildFile; fileRef = 58382694289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58382697289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */ = {isa = PBXBuildFile; fileRef = 58382695289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 39FC9E111C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UIScrollView_InfiniteScroll.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 39FC9E141C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIScrollView_InfiniteScroll.h; sourceTree = ""; }; 39FC9E161C5C7CEE00D130E5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 39FC9E1C1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+InfiniteScroll.h"; path = "Classes/UIScrollView+InfiniteScroll.h"; sourceTree = SOURCE_ROOT; }; - 39FC9E1D1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+InfiniteScroll.m"; path = "Classes/UIScrollView+InfiniteScroll.m"; sourceTree = SOURCE_ROOT; }; + 58382694289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+InfiniteScroll.h"; path = "Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.h"; sourceTree = SOURCE_ROOT; }; + 58382695289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+InfiniteScroll.m"; path = "Sources/UIScrollView_InfiniteScroll/UIScrollView+InfiniteScroll.m"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -50,8 +50,8 @@ 39FC9E131C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll */ = { isa = PBXGroup; children = ( - 39FC9E1C1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.h */, - 39FC9E1D1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.m */, + 58382694289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.h */, + 58382695289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.m */, 39FC9E141C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll.h */, 39FC9E161C5C7CEE00D130E5 /* Info.plist */, ); @@ -66,7 +66,7 @@ buildActionMask = 2147483647; files = ( 39FC9E151C5C7CEE00D130E5 /* UIScrollView_InfiniteScroll.h in Headers */, - 39FC9E1E1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.h in Headers */, + 58382696289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -132,7 +132,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 39FC9E1F1C5C7D3F00D130E5 /* UIScrollView+InfiniteScroll.m in Sources */, + 58382697289FC20C00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -236,9 +236,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = UIScrollView_InfiniteScroll/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "ru.codeispoetry.Infinite-Scroll"; + PRODUCT_BUNDLE_IDENTIFIER = com.github.pronebird.InfiniteScroll; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -253,9 +253,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = UIScrollView_InfiniteScroll/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "ru.codeispoetry.Infinite-Scroll"; + PRODUCT_BUNDLE_IDENTIFIER = com.github.pronebird.InfiniteScroll; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; };