diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 8b3c139..e4ee2e2 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 935BD6E51CE26F0B00E5FDF7 /* Pattern1ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935BD6DE1CE26F0B00E5FDF7 /* Pattern1ViewController.swift */; }; 935BD6E61CE26F0B00E5FDF7 /* Pattern2.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 935BD6DF1CE26F0B00E5FDF7 /* Pattern2.storyboard */; }; 935BD6E71CE26F0B00E5FDF7 /* Pattern2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935BD6E01CE26F0B00E5FDF7 /* Pattern2ViewController.swift */; }; + A16472372051129D00A6D53D /* Pattern3ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16472362051129D00A6D53D /* Pattern3ViewController.swift */; }; + A164723B205117A800A6D53D /* Pattern3.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A164723A205117A800A6D53D /* Pattern3.storyboard */; }; EDA05AD81D92901C00F7C2C5 /* InfiniteCollectionView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDA05AD71D92901900F7C2C5 /* InfiniteCollectionView.framework */; }; EDA05AD91D92901C00F7C2C5 /* InfiniteCollectionView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EDA05AD71D92901900F7C2C5 /* InfiniteCollectionView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -34,7 +36,7 @@ isa = PBXContainerItemProxy; containerPortal = EDA05AD21D92901900F7C2C5 /* InfiniteCollectionView.xcodeproj */; proxyType = 1; - remoteGlobalIDString = 935BD70C1CE26F8E00E5FDF7; + remoteGlobalIDString = OBJ_17; remoteInfo = InfiniteCollectionView; }; /* End PBXContainerItemProxy section */ @@ -67,6 +69,8 @@ 935BD6DE1CE26F0B00E5FDF7 /* Pattern1ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pattern1ViewController.swift; sourceTree = ""; }; 935BD6DF1CE26F0B00E5FDF7 /* Pattern2.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Pattern2.storyboard; sourceTree = ""; }; 935BD6E01CE26F0B00E5FDF7 /* Pattern2ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pattern2ViewController.swift; sourceTree = ""; }; + A16472362051129D00A6D53D /* Pattern3ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pattern3ViewController.swift; sourceTree = ""; }; + A164723A205117A800A6D53D /* Pattern3.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Pattern3.storyboard; sourceTree = ""; }; EDA05AD21D92901900F7C2C5 /* InfiniteCollectionView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = InfiniteCollectionView.xcodeproj; path = ../InfiniteCollectionView.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -104,6 +108,7 @@ children = ( ED510E6D1DDD513200470C8B /* Pattern1 */, ED510E6E1DDD513F00470C8B /* Pattern2 */, + A16472332051126300A6D53D /* Pattern3 */, 935BD6C81CE26EB600E5FDF7 /* AppDelegate.swift */, 935BD6CF1CE26EB600E5FDF7 /* Assets.xcassets */, 935BD6DA1CE26F0B00E5FDF7 /* ImageCollectionViewCell.swift */, @@ -116,6 +121,15 @@ path = Example; sourceTree = ""; }; + A16472332051126300A6D53D /* Pattern3 */ = { + isa = PBXGroup; + children = ( + A164723A205117A800A6D53D /* Pattern3.storyboard */, + A16472362051129D00A6D53D /* Pattern3ViewController.swift */, + ); + name = Pattern3; + sourceTree = ""; + }; ED510E6D1DDD513200470C8B /* Pattern1 */ = { isa = PBXGroup; children = ( @@ -227,6 +241,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A164723B205117A800A6D53D /* Pattern3.storyboard in Resources */, 935BD6D01CE26EB600E5FDF7 /* Assets.xcassets in Resources */, 935BD6E21CE26F0B00E5FDF7 /* ImageCollectionViewCell.xib in Resources */, 935BD6D31CE26EB600E5FDF7 /* LaunchScreen.storyboard in Resources */, @@ -246,6 +261,7 @@ 935BD6C91CE26EB600E5FDF7 /* AppDelegate.swift in Sources */, 935BD6E11CE26F0B00E5FDF7 /* ImageCollectionViewCell.swift in Sources */, 935BD6E31CE26F0B00E5FDF7 /* MainViewController.swift in Sources */, + A16472372051129D00A6D53D /* Pattern3ViewController.swift in Sources */, 935BD6E51CE26F0B00E5FDF7 /* Pattern1ViewController.swift in Sources */, 935BD6E71CE26F0B00E5FDF7 /* Pattern2ViewController.swift in Sources */, ); @@ -379,7 +395,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.github.hryk224.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -393,7 +409,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.github.hryk224.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Example/Example/MainViewController.swift b/Example/Example/MainViewController.swift index 03bc830..17eab1d 100644 --- a/Example/Example/MainViewController.swift +++ b/Example/Example/MainViewController.swift @@ -13,11 +13,13 @@ final class MainViewController: UIViewController { enum Pattern: Int, CustomStringConvertible { case pattern1 case pattern2 - static var count: Int { return 2 } + case pattern3 + static var count: Int { return 3 } var description: String { switch self { case .pattern1: return "pattern1" case .pattern2: return "pattern2" + case .pattern3: return "pattern3" } } } @@ -65,6 +67,9 @@ extension MainViewController: UITableViewDelegate, UITableViewDataSource { case .pattern2: let controller = Pattern2ViewController.createFromStoryboard() navigationController?.pushViewController(controller, animated: true) + case .pattern3: + let controller = Pattern3ViewController.createFromStoryboard() + navigationController?.pushViewController(controller, animated: true) } } } diff --git a/Example/Example/Pattern1ViewController.swift b/Example/Example/Pattern1ViewController.swift index 91f9c34..0c4787f 100644 --- a/Example/Example/Pattern1ViewController.swift +++ b/Example/Example/Pattern1ViewController.swift @@ -39,7 +39,7 @@ final class Pattern1ViewController: UIViewController { let storyboard = UIStoryboard(name: "Pattern1", bundle: nil) return storyboard.instantiateInitialViewController() as! Pattern1ViewController } - func rotate(_ notification: Notification) { + @objc func rotate(_ notification: Notification) { layout.itemSize = UIScreen.main.bounds.size layout.invalidateLayout() collectionView.rotate(notification) diff --git a/Example/Example/Pattern2ViewController.swift b/Example/Example/Pattern2ViewController.swift index 7c70997..5363aaa 100644 --- a/Example/Example/Pattern2ViewController.swift +++ b/Example/Example/Pattern2ViewController.swift @@ -81,7 +81,7 @@ final class InfiniteTableViewCell: UITableViewCell { pageControl.numberOfPages = 4 } } - func rotate(_ notification: Notification) { + @objc func rotate(_ notification: Notification) { let size = CGSize(width: UIScreen.main.bounds.width, height: 239) layout.itemSize = size layout.invalidateLayout() @@ -137,7 +137,7 @@ extension Infinite2TableViewCell: InfiniteCollectionViewDataSource, InfiniteColl func infiniteCollectionView(_ collectionView: UICollectionView, didSelectItemAt usableIndexPath: IndexPath) { print("didSelectItemAt: \(usableIndexPath.item)") } - func rotate(_ notification: Notification) { + @objc func rotate(_ notification: Notification) { collectionView.collectionViewLayout.invalidateLayout() collectionView.rotate(notification) collectionView.layoutIfNeeded() diff --git a/Example/Example/Pattern3.storyboard b/Example/Example/Pattern3.storyboard new file mode 100644 index 0000000..fc73903 --- /dev/null +++ b/Example/Example/Pattern3.storyboard @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Pattern3ViewController.swift b/Example/Example/Pattern3ViewController.swift new file mode 100644 index 0000000..9141837 --- /dev/null +++ b/Example/Example/Pattern3ViewController.swift @@ -0,0 +1,63 @@ +// +// Pattern3ViewController.swift +// Example +// +// Created by iamchiwon on 2018. 3. 8.. +// Copyright © 2018년 hiroyuki yoshida. All rights reserved. +// + +import UIKit +import InfiniteCollectionView + +final class Pattern3ViewController: UIViewController { + var itemsCount: Int = 5 + @IBOutlet weak var collectionView: InfiniteCollectionView! { + didSet { + collectionView.infiniteDataSource = self + collectionView.infiniteDelegate = self + collectionView.register(ImageCollectionViewCell.nib, forCellWithReuseIdentifier: ImageCollectionViewCell.identifier) + } + } + @IBOutlet weak var layout: UICollectionViewFlowLayout! { + didSet { + layout.itemSize = UIScreen.main.bounds.size + } + } + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + NotificationCenter.default.addObserver(self, selector: #selector(Pattern1ViewController.rotate(_:)), name: .UIDeviceOrientationDidChange, object: nil) + } + deinit { + NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil) + } + static func createFromStoryboard() -> Pattern3ViewController { + let storyboard = UIStoryboard(name: "Pattern3", bundle: nil) + return storyboard.instantiateInitialViewController() as! Pattern3ViewController + } + @objc func rotate(_ notification: Notification) { + layout.itemSize = UIScreen.main.bounds.size + layout.invalidateLayout() + collectionView.rotate(notification) + collectionView.layoutIfNeeded() + collectionView.setNeedsLayout() + } +} + +// MARK: - InfiniteCollectionViewDataSource, InfiniteCollectionViewDelegate +extension Pattern3ViewController: InfiniteCollectionViewDataSource, InfiniteCollectionViewDelegate { + func number(ofItems collectionView: UICollectionView) -> Int { + return itemsCount + } + func collectionView(_ collectionView: UICollectionView, dequeueForItemAt dequeueIndexPath: IndexPath, cellForItemAt usableIndexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCollectionViewCell.identifier, for: dequeueIndexPath) as! ImageCollectionViewCell + cell.configure(indexPath: usableIndexPath) + return cell + } + func infiniteCollectionView(_ collectionView: UICollectionView, didSelectItemAt usableIndexPath: IndexPath) { + print("didSelectItemAt: \(usableIndexPath.item)") + } + func scrollView(_ scrollView: UIScrollView, pageIndex: Int) { + print("scrollView: \(pageIndex)") + } +} + diff --git a/InfiniteCollectionView.xcodeproj/project.pbxproj b/InfiniteCollectionView.xcodeproj/project.pbxproj index 51d0cda..fb09b77 100644 --- a/InfiniteCollectionView.xcodeproj/project.pbxproj +++ b/InfiniteCollectionView.xcodeproj/project.pbxproj @@ -135,6 +135,7 @@ PRODUCT_BUNDLE_IDENTIFIER = InfiniteCollectionView; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SWIFT_VERSION = 4.0; TARGET_NAME = InfiniteCollectionView; }; name = Debug; @@ -155,6 +156,7 @@ PRODUCT_BUNDLE_IDENTIFIER = InfiniteCollectionView; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SWIFT_VERSION = 4.0; TARGET_NAME = InfiniteCollectionView; }; name = Release; diff --git a/Sources/InfiniteCollectionView.swift b/Sources/InfiniteCollectionView.swift index 5111dde..1080813 100644 --- a/Sources/InfiniteCollectionView.swift +++ b/Sources/InfiniteCollectionView.swift @@ -46,7 +46,7 @@ open class InfiniteCollectionView: UICollectionView { deinit { NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil) } - open func rotate(_ notification: Notification) { + @objc open func rotate(_ notification: Notification) { setContentOffset(CGPoint(x: CGFloat(pageIndex + indexOffset) * itemWidth, y: contentOffset.y), animated: false) } open override func selectItem(at indexPath: IndexPath?, animated: Bool, scrollPosition: UICollectionViewScrollPosition) { @@ -76,10 +76,18 @@ private extension InfiniteCollectionView { guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 } return layout.itemSize.width + layout.minimumInteritemSpacing } + var itemHeight: CGFloat { + guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 } + return layout.itemSize.height + layout.minimumLineSpacing + } var totalContentWidth: CGFloat { let numberOfCells: CGFloat = CGFloat(infiniteDataSource?.number(ofItems: self) ?? 0) return numberOfCells * itemWidth } + var totalContentHeight: CGFloat { + let numberOfCells: CGFloat = CGFloat(infiniteDataSource?.number(ofItems: self) ?? 0) + return numberOfCells * itemHeight + } func configure() { delegate = self dataSource = self @@ -103,6 +111,21 @@ private extension InfiniteCollectionView { indexOffset += offset reloadData() } + let centerY = (scrollView.contentSize.height - bounds.height) / 2 + let distFromCenterY = centerY - currentOffset.y + if fabs(distFromCenterY) > (totalContentHeight / 4) { + let cellcount = distFromCenterY / itemHeight + let shiftCells = Int((cellcount > 0) ? floor(cellcount) : ceil(cellcount)) + let offsetCorrection = (abs(cellcount).truncatingRemainder(dividingBy: 1)) * itemHeight + if centerY > contentOffset.y { + contentOffset = CGPoint(x: currentOffset.x, y: centerY - offsetCorrection) + } else { + contentOffset = CGPoint(x: currentOffset.x, y: centerY + offsetCorrection) + } + let offset = correctedIndex(shiftCells) + indexOffset += offset + reloadData() + } let centerPoint = CGPoint(x: scrollView.frame.size.width / 2 + scrollView.contentOffset.x, y: scrollView.frame.size.height / 2 + scrollView.contentOffset.y) guard let indexPath = indexPathForItem(at: centerPoint) else { return } pageIndex = correctedIndex(indexPath.item - indexOffset)