From 0657521eba1fbfca7fa8f63546713f3a52dca3f2 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:03:56 +0200 Subject: [PATCH 01/20] Clean up .gitignore --- .gitignore | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) 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/ From 1a4c6875f6536349078dcb27de9dbcd455f05e28 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:09:38 +0200 Subject: [PATCH 02/20] Use default SPM layout for library --- .jazzy.yml | 4 +-- .../project.pbxproj | 25 ++++++++++--------- Package.swift | 9 +++---- .../UIScrollView+InfiniteScroll.h | 2 +- .../UIScrollView+InfiniteScroll.m | 2 +- UIScrollView-InfiniteScroll.podspec | 2 +- .../project.pbxproj | 16 ++++++------ 7 files changed, 30 insertions(+), 30 deletions(-) rename {Classes => Sources/UIScrollView_InfiniteScroll}/UIScrollView+InfiniteScroll.h (98%) rename {Classes => Sources/UIScrollView_InfiniteScroll}/UIScrollView+InfiniteScroll.m (99%) diff --git a/.jazzy.yml b/.jazzy.yml index bd26c1a..b258c62 100644 --- a/.jazzy.yml +++ b/.jazzy.yml @@ -12,5 +12,5 @@ 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/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index 42f0a01..ebfc842 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -16,8 +16,9 @@ 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 */; }; + 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 */; }; 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */; }; 5869B32D25D8EB5200550415 /* Main-tvOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */; }; 5869B33025D8EB9A00550415 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF21AF6ADCA0075D88A /* TableViewController.swift */; }; @@ -27,7 +28,6 @@ 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 */ @@ -45,9 +45,9 @@ 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 = ""; }; 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 = ""; }; @@ -94,6 +94,7 @@ 58279BC01AF6AC5D0075D88A /* InfiniteScrollViewDemoSwift */ = { isa = PBXGroup; children = ( + 5838269F289FC76B00EEF6A1 /* UIScrollView_InfiniteScroll */, 5809B37A1D247F980029136A /* Launch Screen.storyboard */, 58279BF01AF6ADA60075D88A /* Main.storyboard */, 58279BC31AF6AC5D0075D88A /* AppDelegate.swift */, @@ -102,7 +103,6 @@ 581FB78C2083B850009BF7B8 /* Support.swift */, 58279BF21AF6ADCA0075D88A /* TableViewController.swift */, 58279BCC1AF6AC5D0075D88A /* Images.xcassets */, - 58279BEF1AF6AD2C0075D88A /* Classes */, 582A4AC11DD8AB2D008203BA /* Localizable.strings */, 58A7C9231E0969E80084C93C /* Models */, 58279BC11AF6AC5D0075D88A /* Supporting Files */, @@ -119,14 +119,15 @@ 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; @@ -258,11 +259,11 @@ 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 */, 58279BF31AF6ADCA0075D88A /* TableViewController.swift in Sources */, - 58279C081AF6DC9C0075D88A /* UIScrollView+InfiniteScroll.m in Sources */, 58279BC41AF6AC5D0075D88A /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -271,7 +272,7 @@ 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 */, 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/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..f8116cb 100644 --- a/UIScrollView-InfiniteScroll.podspec +++ b/UIScrollView-InfiniteScroll.podspec @@ -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..ec46aaf 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; }; From cdd1a769f36c00c6897c6b61623ac95a00579610 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:17:06 +0200 Subject: [PATCH 03/20] Add swiftformat configuration --- .swiftformat | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .swiftformat 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 From 60abc8434a1bb64968b9e7f40873a5b4b219ddd1 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:18:47 +0200 Subject: [PATCH 04/20] Demo: combine models into single file --- .../project.pbxproj | 27 ++++---------- .../FlickrResponse.swift | 21 ----------- .../HackerNewsResponse.swift | 24 ------------- InfiniteScrollViewDemoSwift/Models.swift | 36 +++++++++++++++++++ 4 files changed, 42 insertions(+), 66 deletions(-) delete mode 100644 InfiniteScrollViewDemoSwift/FlickrResponse.swift delete mode 100644 InfiniteScrollViewDemoSwift/HackerNewsResponse.swift create mode 100644 InfiniteScrollViewDemoSwift/Models.swift diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index ebfc842..1b84461 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -8,8 +8,6 @@ /* 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 */; }; @@ -19,6 +17,8 @@ 582A4ABF1DD8AB2D008203BA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 582A4AC11DD8AB2D008203BA /* Localizable.strings */; }; 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 */; }; 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */; }; 5869B32D25D8EB5200550415 /* Main-tvOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */; }; 5869B33025D8EB9A00550415 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF21AF6ADCA0075D88A /* TableViewController.swift */; }; @@ -26,15 +26,11 @@ 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 */; }; 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 = ""; }; @@ -48,11 +44,11 @@ 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 */ @@ -102,9 +98,9 @@ 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */, 581FB78C2083B850009BF7B8 /* Support.swift */, 58279BF21AF6ADCA0075D88A /* TableViewController.swift */, + 583826CE289FD02500EEF6A1 /* Models.swift */, 58279BCC1AF6AC5D0075D88A /* Images.xcassets */, 582A4AC11DD8AB2D008203BA /* Localizable.strings */, - 58A7C9231E0969E80084C93C /* Models */, 58279BC11AF6AC5D0075D88A /* Supporting Files */, ); path = InfiniteScrollViewDemoSwift; @@ -139,15 +135,6 @@ path = "InfiniteScrollViewDemo-tvOS"; sourceTree = ""; }; - 58A7C9231E0969E80084C93C /* Models */ = { - isa = PBXGroup; - children = ( - 58A7C91D1E09641A0084C93C /* FlickrResponse.swift */, - 581FB78A2083B7E0009BF7B8 /* HackerNewsResponse.swift */, - ); - name = Models; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -258,11 +245,10 @@ 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 */, 58279BC41AF6AC5D0075D88A /* AppDelegate.swift in Sources */, ); @@ -276,10 +262,9 @@ 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; }; 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/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 +} From fa78780b2175b75da46511bc96d10e1cebf10939 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:22:54 +0200 Subject: [PATCH 05/20] Demo: drop start/stop time. always use global --- InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift index a4c7d92..0a21546 100644 --- a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift +++ b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift @@ -27,8 +27,6 @@ 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 @@ -121,17 +119,13 @@ class CustomInfiniteIndicator: UIView { 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() { From 3b7cbda31227915b6b3d9cbc4b26330210776630 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:23:12 +0200 Subject: [PATCH 06/20] Demo: call super in traitCollectionDidChange --- InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift index 0a21546..7e98163 100644 --- a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift +++ b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift @@ -64,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() From 173c097e3cb6e16ca31fde9c210fe9a2d5d81b95 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:32:38 +0200 Subject: [PATCH 07/20] Demo: drop support file --- .../project.pbxproj | 5 -- .../CollectionViewController.swift | 88 +++++++++++-------- InfiniteScrollViewDemoSwift/Support.swift | 50 ----------- .../TableViewController.swift | 74 +++++++++------- .../en.lproj/Localizable.strings | 9 -- 5 files changed, 93 insertions(+), 133 deletions(-) delete mode 100644 InfiniteScrollViewDemoSwift/Support.swift diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index 1b84461..28eb10d 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */; }; 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 */; }; @@ -31,7 +30,6 @@ /* Begin PBXFileReference section */ 5809B37A1D247F980029136A /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; 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 = ""; }; @@ -96,7 +94,6 @@ 58279BC31AF6AC5D0075D88A /* AppDelegate.swift */, 58279BF41AF6AEDA0075D88A /* CollectionViewController.swift */, 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */, - 581FB78C2083B850009BF7B8 /* Support.swift */, 58279BF21AF6ADCA0075D88A /* TableViewController.swift */, 583826CE289FD02500EEF6A1 /* Models.swift */, 58279BCC1AF6AC5D0075D88A /* Images.xcassets */, @@ -244,7 +241,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 581FB78D2083B850009BF7B8 /* Support.swift in Sources */, 583826A2289FC76B00EEF6A1 /* UIScrollView+InfiniteScroll.m in Sources */, 58279C011AF6C8650075D88A /* CustomInfiniteIndicator.swift in Sources */, 58279BF51AF6AEDA0075D88A /* CollectionViewController.swift in Sources */, @@ -263,7 +259,6 @@ 5869B33025D8EB9A00550415 /* TableViewController.swift in Sources */, 5869B33625D8EBA900550415 /* CustomInfiniteIndicator.swift in Sources */, 5869B33C25D8EBAE00550415 /* AppDelegate.swift in Sources */, - 5869B33325D8EB9E00550415 /* Support.swift in Sources */, 583826D0289FD02500EEF6A1 /* Models.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/InfiniteScrollViewDemoSwift/CollectionViewController.swift b/InfiniteScrollViewDemoSwift/CollectionViewController.swift index 996b16f..5048532 100644 --- a/InfiniteScrollViewDemoSwift/CollectionViewController.swift +++ b/InfiniteScrollViewDemoSwift/CollectionViewController.swift @@ -51,23 +51,26 @@ class CollectionViewController: UICollectionViewController { 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,32 +79,30 @@ 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) + }) } } } @@ -248,24 +249,39 @@ class PhotoCell: UICollectionViewCell { // 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/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..d436673 100644 --- a/InfiniteScrollViewDemoSwift/TableViewController.swift +++ b/InfiniteScrollViewDemoSwift/TableViewController.swift @@ -53,43 +53,40 @@ class TableViewController: UITableViewController { // 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.. - - 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 index be005ee..bcbdc8b 100644 --- a/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings +++ b/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings @@ -7,15 +7,6 @@ /* 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"; From dfd08243c7a19d1ad0bcd7a8b261885f9414d0a2 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:33:42 +0200 Subject: [PATCH 08/20] Carthage: align deployment target with podspec (9.0) --- UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj b/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj index ec46aaf..7c3bbb3 100644 --- a/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj +++ b/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj @@ -236,7 +236,7 @@ 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_NAME = "$(TARGET_NAME)"; @@ -253,7 +253,7 @@ 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_NAME = "$(TARGET_NAME)"; From d1941cf724a871e016762d8c7ab1a039937c278d Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:34:04 +0200 Subject: [PATCH 09/20] Carthage: update bundle identifier to com.github.pronebird.* --- UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj b/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj index 7c3bbb3..925f53d 100644 --- a/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj +++ b/UIScrollView_InfiniteScroll.xcodeproj/project.pbxproj @@ -238,7 +238,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 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; }; @@ -255,7 +255,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 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; }; From 63f5ce601d259c63dc0c857e12ce035464a30b67 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:34:23 +0200 Subject: [PATCH 10/20] License: update copyright year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 03052dadb9fd559b081e59743ff841c8fbc7ba0b Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:34:30 +0200 Subject: [PATCH 11/20] Update README --- README.md | 144 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 47 deletions(-) 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 From 1a5b0edf05237c3d93d3ec24bfe8faea13b243a2 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:34:54 +0200 Subject: [PATCH 12/20] Bump 1.3.0 --- .jazzy.yml | 2 +- CHANGES | 2 +- UIScrollView-InfiniteScroll.podspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.jazzy.yml b/.jazzy.yml index b258c62..9871058 100644 --- a/.jazzy.yml +++ b/.jazzy.yml @@ -5,7 +5,7 @@ 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 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/UIScrollView-InfiniteScroll.podspec b/UIScrollView-InfiniteScroll.podspec index f8116cb..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' From 1c2404ccae1faa3efba146d9d6617cc993d94977 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:41:02 +0200 Subject: [PATCH 13/20] Demo: set swift 5.0 in project settings --- InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index 28eb10d..8e69ab6 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -337,6 +337,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -385,6 +386,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -402,7 +404,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; @@ -418,7 +419,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; @@ -448,7 +448,6 @@ 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; }; @@ -476,7 +475,6 @@ 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; }; From 14892312b1c70db3e8b842002f10be282a2560a2 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:45:59 +0200 Subject: [PATCH 14/20] Demo: use default C compiler --- InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index 8e69ab6..73e243e 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -289,8 +289,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; @@ -317,7 +315,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; @@ -346,8 +343,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; @@ -374,7 +369,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; @@ -430,7 +424,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; @@ -438,7 +431,6 @@ 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; @@ -460,14 +452,12 @@ 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; From 894342b4fd846647e8ffe89c4582a048c84e2731 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:47:25 +0200 Subject: [PATCH 15/20] Demo: drop irrelevant xc configuration --- InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj index 73e243e..9a73df6 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -330,7 +330,6 @@ 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"; @@ -377,7 +376,6 @@ 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; @@ -433,8 +431,6 @@ DEVELOPMENT_TEAM = ZXGXFAG6WF; 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; @@ -460,7 +456,6 @@ DEVELOPMENT_TEAM = ZXGXFAG6WF; 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; From 280ff1d21e77c419f5599a9f8b0dfaac0d29278b Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 15:50:42 +0200 Subject: [PATCH 16/20] Commit IDEWorkspaceChecks.plist --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 InfiniteScrollViewDemoSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist 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 + + + From 445f5ad0c6f075498f2adc2b007940f0ebd85051 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 16:12:16 +0200 Subject: [PATCH 17/20] Demo: drop localization, provide on default value instead --- .../Base.lproj/LaunchScreen.storyboard | 24 --------------- .../project.pbxproj | 30 +------------------ .../CollectionViewController.swift | 8 ++--- InfiniteScrollViewDemoSwift/Info.plist | 2 +- .../TableViewController.swift | 6 ++-- .../en.lproj/Localizable.strings | 18 ----------- 6 files changed, 9 insertions(+), 79 deletions(-) delete mode 100644 InfiniteScrollViewDemo-tvOS/Base.lproj/LaunchScreen.storyboard delete mode 100644 InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings 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 9a73df6..0b268a8 100644 --- a/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj +++ b/InfiniteScrollViewDemoSwift.xcodeproj/project.pbxproj @@ -14,12 +14,10 @@ 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 */; }; - 582A4ABF1DD8AB2D008203BA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 582A4AC11DD8AB2D008203BA /* Localizable.strings */; }; 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 */; }; - 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */; }; 5869B32D25D8EB5200550415 /* Main-tvOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */; }; 5869B33025D8EB9A00550415 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279BF21AF6ADCA0075D88A /* TableViewController.swift */; }; 5869B33625D8EBA900550415 /* CustomInfiniteIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58279C001AF6C8650075D88A /* CustomInfiniteIndicator.swift */; }; @@ -39,12 +37,10 @@ 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 = ""; }; - 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 = ""; }; /* End PBXFileReference section */ @@ -97,7 +93,6 @@ 58279BF21AF6ADCA0075D88A /* TableViewController.swift */, 583826CE289FD02500EEF6A1 /* Models.swift */, 58279BCC1AF6AC5D0075D88A /* Images.xcassets */, - 582A4AC11DD8AB2D008203BA /* Localizable.strings */, 58279BC11AF6AC5D0075D88A /* Supporting Files */, ); path = InfiniteScrollViewDemoSwift; @@ -126,7 +121,6 @@ isa = PBXGroup; children = ( 5869B32C25D8EB5200550415 /* Main-tvOS.storyboard */, - 5869B31F25D8EB0400550415 /* LaunchScreen.storyboard */, 5869B32225D8EB0400550415 /* Info.plist */, ); path = "InfiniteScrollViewDemo-tvOS"; @@ -194,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, ); @@ -218,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 */, ); @@ -229,7 +221,6 @@ buildActionMask = 2147483647; files = ( 5869B34A25D8EF4D00550415 /* Images.xcassets in Resources */, - 5869B32125D8EB0400550415 /* LaunchScreen.storyboard in Resources */, 5869B32D25D8EB5200550415 /* Main-tvOS.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -265,25 +256,6 @@ }; /* 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; diff --git a/InfiniteScrollViewDemoSwift/CollectionViewController.swift b/InfiniteScrollViewDemoSwift/CollectionViewController.swift index 5048532..0e55bec 100644 --- a/InfiniteScrollViewDemoSwift/CollectionViewController.swift +++ b/InfiniteScrollViewDemoSwift/CollectionViewController.swift @@ -108,15 +108,15 @@ class CollectionViewController: UICollectionViewController { } fileprivate func showAlertWithError(_ error: Error) { - let alert = UIAlertController(title: NSLocalizedString("collectionView.errorAlert.title", comment: ""), + let alert = UIAlertController(title: NSLocalizedString("collectionView.errorAlert.title", value: "Failed to fetch data", comment: ""), message: error.localizedDescription, preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.dismiss", comment: ""), + + alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.dismiss", value: "Dismiss", comment: ""), style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.retry", comment: ""), + alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.retry", value: "Retry", comment: ""), style: .default, handler: { _ in self.performFetch(nil) })) diff --git a/InfiniteScrollViewDemoSwift/Info.plist b/InfiniteScrollViewDemoSwift/Info.plist index 7ef1223..6af5417 100644 --- a/InfiniteScrollViewDemoSwift/Info.plist +++ b/InfiniteScrollViewDemoSwift/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/InfiniteScrollViewDemoSwift/TableViewController.swift b/InfiniteScrollViewDemoSwift/TableViewController.swift index d436673..ed0cf20 100644 --- a/InfiniteScrollViewDemoSwift/TableViewController.swift +++ b/InfiniteScrollViewDemoSwift/TableViewController.swift @@ -91,15 +91,15 @@ class TableViewController: UITableViewController { } fileprivate func showAlertWithError(_ error: Error) { - let alert = UIAlertController(title: NSLocalizedString("tableView.errorAlert.title", comment: ""), + let alert = UIAlertController(title: NSLocalizedString("tableView.errorAlert.title", value: "Failed to fetch data", comment: ""), message: error.localizedDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("tableView.errorAlert.dismiss", comment: ""), + alert.addAction(UIAlertAction(title: NSLocalizedString("tableView.errorAlert.dismiss", value: "Dismiss", comment: ""), style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: NSLocalizedString("tableView.errorAlert.retry", comment: ""), + alert.addAction(UIAlertAction(title: NSLocalizedString("tableView.errorAlert.retry", value: "Retry", comment: ""), style: .default, handler: { _ in self.performFetch(nil) })) diff --git a/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings b/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings deleted file mode 100644 index bcbdc8b..0000000 --- a/InfiniteScrollViewDemoSwift/en.lproj/Localizable.strings +++ /dev/null @@ -1,18 +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. */ -"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"; - From 9af2720ea347398388f0333f42ebc9c9c84243ac Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 16:17:52 +0200 Subject: [PATCH 18/20] Demo: allow arbitrary loads in safari on iOS 10+ --- InfiniteScrollViewDemoSwift/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InfiniteScrollViewDemoSwift/Info.plist b/InfiniteScrollViewDemoSwift/Info.plist index 6af5417..e6a1b5b 100644 --- a/InfiniteScrollViewDemoSwift/Info.plist +++ b/InfiniteScrollViewDemoSwift/Info.plist @@ -26,6 +26,8 @@ NSAllowsArbitraryLoads + NSAllowsArbitraryLoadsInWebContent + UILaunchStoryboardName Launch Screen From cfc1708c925399dfe41811e67aee8872bd31a932 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 16:22:43 +0200 Subject: [PATCH 19/20] Demo: format code --- InfiniteScrollViewDemoSwift/AppDelegate.swift | 7 +- .../CollectionViewController.swift | 167 +++++++++++------- .../CustomInfiniteIndicator.swift | 76 ++++---- .../TableViewController.swift | 109 ++++++------ 4 files changed, 211 insertions(+), 148 deletions(-) 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 0e55bec..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,24 +30,27 @@ 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() } @@ -81,7 +83,7 @@ class CollectionViewController: UICollectionViewController { } } } - + fileprivate func performFetch(_ completionHandler: (() -> Void)?) { fetchData { response, error in if let error = error { @@ -106,63 +108,92 @@ class CollectionViewController: UICollectionViewController { } } } - + fileprivate func showAlertWithError(_ error: Error) { - let alert = UIAlertController(title: NSLocalizedString("collectionView.errorAlert.title", value: "Failed to fetch data", comment: ""), - message: error.localizedDescription, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.dismiss", value: "Dismiss", comment: ""), - style: .cancel, - handler: nil)) - - alert.addAction(UIAlertAction(title: NSLocalizedString("collectionView.errorAlert.retry", value: "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 } } @@ -170,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 } } @@ -196,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, *) { @@ -244,7 +282,6 @@ class PhotoCell: UICollectionViewCell { imageView.backgroundColor = UIColor(white: 0.95, alpha: 1) } } - } // MARK: - API diff --git a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift index 7e98163..54b2877 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() @@ -29,29 +29,29 @@ class CustomInfiniteIndicator: UIView { private let outerCircle = CAShapeLayer() // 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() } @@ -90,12 +90,12 @@ class CustomInfiniteIndicator: UIView { isHidden = true removeAnimation() } - + // MARK: - Private - + private func commonInit() { registerForAppStateNotifications() - + isHidden = true backgroundColor = UIColor.clear @@ -106,63 +106,80 @@ 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 = layer.convertTime(CACurrentMediaTime(), from: nil) layer.add(anim, forKey: rotationAnimationKey) } - + private func removeAnimation() { layer.removeAnimation(forKey: rotationAnimationKey) } - + @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 } @@ -171,7 +188,7 @@ class CustomInfiniteIndicator: UIView { let defaultLightColor = UIColor.gray.withAlphaComponent(0.2) if #available(iOS 13.0, tvOS 13, *) { - return UIColor { (traitCollection) -> UIColor in + return UIColor { traitCollection -> UIColor in switch traitCollection.userInterfaceStyle { case .light, .unspecified: return defaultLightColor @@ -185,5 +202,4 @@ class CustomInfiniteIndicator: UIView { return defaultLightColor } } - } diff --git a/InfiniteScrollViewDemoSwift/TableViewController.swift b/InfiniteScrollViewDemoSwift/TableViewController.swift index ed0cf20..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,20 +36,20 @@ 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 @@ -64,7 +63,7 @@ class TableViewController: UITableViewController { // load initial data tableView.beginInfiniteScroll(true) } - + fileprivate func performFetch(_ completionHandler: (() -> Void)?) { fetchData { response, error in if let error = error { @@ -89,91 +88,101 @@ class TableViewController: UITableViewController { completionHandler?() } } - + fileprivate func showAlertWithError(_ error: Error) { - let alert = UIAlertController(title: NSLocalizedString("tableView.errorAlert.title", value: "Failed to fetch data", comment: ""), - message: error.localizedDescription, - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: NSLocalizedString("tableView.errorAlert.dismiss", value: "Dismiss", comment: ""), - style: .cancel, - handler: nil)) - - alert.addAction(UIAlertAction(title: NSLocalizedString("tableView.errorAlert.retry", value: "Retry", comment: ""), - style: .default, - handler: { _ in self.performFetch(nil) })) - - present(alert, animated: true, completion: nil) + let alertController = UIAlertController( + title: NSLocalizedString( + "tableView.errorAlert.title", + value: "Failed to fetch data", + comment: "" + ), + message: error.localizedDescription, + preferredStyle: .alert + ) + + alertController.addAction(UIAlertAction( + title: NSLocalizedString("tableView.errorAlert.dismiss", value: "Dismiss", comment: ""), + style: .cancel, + handler: nil + )) + + alertController.addAction(UIAlertAction( + title: NSLocalizedString( + "tableView.errorAlert.retry", + value: "Retry", + comment: "" + ), + style: .default, + handler: { _ in self.performFetch(nil) } + )) + + present(alertController, animated: true, completion: nil) } - } // MARK: - Actions extension TableViewController { - @IBAction func handleRefresh() { tableView.beginInfiniteScroll(true) } - } // MARK: - UITableViewDelegate extension TableViewController { - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let story = stories[indexPath.row] let url = story.url ?? story.postUrl - + #if !os(tvOS) - let safariController = SFSafariViewController(url: url) - safariController.delegate = self - - let safariNavigationController = UINavigationController(rootViewController: safariController) - safariNavigationController.setNavigationBarHidden(true, animated: false) - - present(safariNavigationController, animated: true) + let safariController = SFSafariViewController(url: url) + safariController.delegate = self + + let safariNavigationController = + UINavigationController(rootViewController: safariController) + safariNavigationController.setNavigationBarHidden(true, animated: false) + + present(safariNavigationController, animated: true) #endif - + tableView.deselectRow(at: indexPath, animated: true) } - } // MARK: - UITableViewDataSource extension TableViewController { - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> 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 From 52d45f9c35eb148c76e52b54bf3631a432ddfe64 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Sun, 7 Aug 2022 16:28:05 +0200 Subject: [PATCH 20/20] Demo: use .systemGray4 on iOS 13+ --- .../CustomInfiniteIndicator.swift | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift index 54b2877..c955cab 100644 --- a/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift +++ b/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift @@ -185,21 +185,10 @@ class CustomInfiniteIndicator: UIView { } 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) } } }