You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/Session/Media Viewing & Editing/GIFs/GifPickerLayout.swift

142 lines
5.0 KiB
Swift

// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
Merge remote-tracking branch 'origin/feature/swift-package-manager' into feature/groups-rebuild # Conflicts: # Podfile # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Calls/Call Management/SessionCall.swift # Session/Calls/Call Management/SessionCallManager.swift # Session/Calls/CallVC.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/ConversationVC.swift # Session/Conversations/ConversationViewModel.swift # Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Emoji/Emoji+Available.swift # Session/Home/GlobalSearch/GlobalSearchViewController.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Home/New Conversation/NewDMVC.swift # Session/Media Viewing & Editing/DocumentTitleViewController.swift # Session/Media Viewing & Editing/GIFs/GifPickerCell.swift # Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift # Session/Media Viewing & Editing/ImagePickerController.swift # Session/Media Viewing & Editing/MediaTileViewController.swift # Session/Media Viewing & Editing/PhotoCapture.swift # Session/Media Viewing & Editing/PhotoCaptureViewController.swift # Session/Media Viewing & Editing/PhotoLibrary.swift # Session/Media Viewing & Editing/SendMediaNavigationController.swift # Session/Meta/AppDelegate.swift # Session/Meta/AppEnvironment.swift # Session/Meta/MainAppContext.swift # Session/Meta/SessionApp.swift # Session/Notifications/NotificationPresenter.swift # Session/Notifications/PushRegistrationManager.swift # Session/Notifications/SyncPushTokensJob.swift # Session/Notifications/UserNotificationsAdaptee.swift # Session/Onboarding/LandingVC.swift # Session/Onboarding/LinkDeviceVC.swift # Session/Onboarding/Onboarding.swift # Session/Onboarding/RegisterVC.swift # Session/Onboarding/RestoreVC.swift # Session/Settings/HelpViewModel.swift # Session/Settings/NukeDataModal.swift # Session/Shared/FullConversationCell.swift # Session/Shared/OWSBezierPathView.m # Session/Utilities/BackgroundPoller.swift # Session/Utilities/MockDataGenerator.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift # SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift # SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift # SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift # SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift # SessionMessagingKit/Database/Models/Attachment.swift # SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift # SessionMessagingKit/Database/Models/Interaction.swift # SessionMessagingKit/Database/Models/Profile.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/File Server/FileServerAPI.swift # SessionMessagingKit/Jobs/AttachmentDownloadJob.swift # SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift # SessionMessagingKit/Jobs/DisappearingMessagesJob.swift # SessionMessagingKit/Jobs/FailedMessageSendsJob.swift # SessionMessagingKit/Jobs/MessageSendJob.swift # SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+Contacts.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+ConvoInfoVolatile.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift # SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift # SessionMessagingKit/Messages/Message.swift # SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroupAPI.swift # SessionMessagingKit/Open Groups/Models/SOGSMessage.swift # SessionMessagingKit/Open Groups/OpenGroupAPI.swift # SessionMessagingKit/Open Groups/OpenGroupManager.swift # SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+LegacyClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift # SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift # SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift # SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupAPI+Poller.swift # SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift # SessionMessagingKitTests/LibSession/LibSessionSpec.swift # SessionMessagingKitTests/LibSession/LibSessionUtilSpec.swift # SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift # SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift # SessionNotificationServiceExtension/NSENotificationPresenter.swift # SessionNotificationServiceExtension/NotificationServiceExtension.swift # SessionShareExtension/ShareAppExtensionContext.swift # SessionShareExtension/ShareNavController.swift # SessionShareExtension/ThreadPickerVC.swift # SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift # SessionSnodeKit/Models/DeleteAllBeforeResponse.swift # SessionSnodeKit/Models/DeleteAllMessagesResponse.swift # SessionSnodeKit/Models/DeleteMessagesResponse.swift # SessionSnodeKit/Models/RevokeSubkeyRequest.swift # SessionSnodeKit/Models/RevokeSubkeyResponse.swift # SessionSnodeKit/Models/SendMessageResponse.swift # SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift # SessionSnodeKit/Models/UpdateExpiryAllResponse.swift # SessionSnodeKit/Models/UpdateExpiryResponse.swift # SessionSnodeKit/Networking/SnodeAPI.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionTests/Database/DatabaseSpec.swift # SessionTests/Settings/NotificationContentViewModelSpec.swift # SessionUIKit/Components/ToastController.swift # SessionUIKit/Style Guide/Values.swift # SessionUtilitiesKit/Crypto/Crypto+SessionUtilitiesKit.swift # SessionUtilitiesKit/Crypto/Crypto.swift # SessionUtilitiesKit/Database/Models/Identity.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/Database/Storage.swift # SessionUtilitiesKit/Database/Types/Migration.swift # SessionUtilitiesKit/General/AppContext.swift # SessionUtilitiesKit/General/Data+Utilities.swift # SessionUtilitiesKit/General/Logging.swift # SessionUtilitiesKit/General/SNUserDefaults.swift # SessionUtilitiesKit/General/String+Trimming.swift # SessionUtilitiesKit/General/String+Utilities.swift # SessionUtilitiesKit/General/TimestampUtils.swift # SessionUtilitiesKit/General/UIEdgeInsets.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift # SessionUtilitiesKit/LibSession/LibSessionError.swift # SessionUtilitiesKit/Media/DataSource.swift # SessionUtilitiesKit/Meta/SessionUtilitiesKit.h # SessionUtilitiesKit/Networking/NetworkType.swift # SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift # SessionUtilitiesKit/Utilities/BackgroundTaskManager.swift # SessionUtilitiesKit/Utilities/BencodeResponse.swift # SessionUtilitiesKit/Utilities/CExceptionHelper.mm # SessionUtilitiesKit/Utilities/FileManagerType.swift # SessionUtilitiesKit/Utilities/KeychainStorageType.swift # SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift # SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift # SessionUtilitiesKitTests/General/SessionIdSpec.swift # SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift # SignalUtilitiesKit/Configuration.swift # SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift # SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift # SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift # SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift # SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift # SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift # SignalUtilitiesKit/Meta/SignalUtilitiesKit.h # SignalUtilitiesKit/Shared View Controllers/OWSViewController.swift # SignalUtilitiesKit/Shared Views/CircleView.swift # SignalUtilitiesKit/Shared Views/TappableView.swift # SignalUtilitiesKit/Utilities/AppSetup.swift # SignalUtilitiesKit/Utilities/Bench.swift # SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift # _SharedTestUtilities/CommonMockedExtensions.swift # _SharedTestUtilities/MockCrypto.swift # _SharedTestUtilities/Mocked.swift # _SharedTestUtilities/SynchronousStorage.swift
10 months ago
import UIKit
protocol GifPickerLayoutDelegate: AnyObject {
func imageInfosForLayout() -> [GiphyImageInfo]
}
// A Pinterest-style waterfall layout.
class GifPickerLayout: UICollectionViewLayout {
public weak var delegate: GifPickerLayoutDelegate?
private var itemAttributesMap = [UInt: UICollectionViewLayoutAttributes]()
private var contentSize = CGSize.zero
// MARK: Initializers and Factory Methods
@available(*, unavailable, message:"use other constructor instead.")
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init() {
super.init()
}
// MARK: Methods
override func invalidateLayout() {
super.invalidateLayout()
itemAttributesMap.removeAll()
}
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
7 years ago
super.invalidateLayout(with: context)
itemAttributesMap.removeAll()
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else {
return
}
guard let delegate = delegate else {
return
}
let vInset = UInt(5)
let hInset = UInt(5)
let vSpacing = UInt(3)
let hSpacing = UInt(3)
// We use 2 or 3 columns, depending on the device.
// 2 columns will show fewer GIFs at a time,
// but use less network & be a more responsive experience.
let columnCount = UInt(2)
let totalViewWidth = UInt(collectionView.bounds.width)
let hTotalWhitespace = (2 * hInset) + (hSpacing * (columnCount - 1))
let hRemainderSpace = totalViewWidth - hTotalWhitespace
let columnWidth = UInt(hRemainderSpace / columnCount)
// We want to unevenly distribute the hSpacing between the columns
// so that the left and right margins are equal, which is non-trivial
// due to rounding error.
let totalHSpacing = totalViewWidth - ((2 * hInset) + (columnCount * columnWidth))
// columnXs are the left edge of each column.
var columnXs = [UInt]()
// columnYs are the top edge of the next cell in each column.
var columnYs = [UInt]()
for columnIndex in 0...columnCount-1 {
var columnX = hInset + (columnWidth * columnIndex)
if columnCount > 1 {
// We want to unevenly distribute the hSpacing between the columns
// so that the left and right margins are equal, which is non-trivial
// due to rounding error.
columnX += ((totalHSpacing * columnIndex) / (columnCount - 1))
}
columnXs.append(columnX)
columnYs.append(vInset)
}
// Always layout all items.
let imageInfos = delegate.imageInfosForLayout()
var contentBottom = vInset
for (cellIndex, imageInfo) in imageInfos.enumerated() {
// Select a column by finding the "highest, leftmost" column.
var column = 0
var cellY = columnYs[column]
for (columnValue, columnYValue) in columnYs.enumerated() {
if columnYValue < cellY {
column = columnValue
cellY = columnYValue
}
}
let cellX = columnXs[column]
let cellWidth = columnWidth
let cellHeight = UInt(columnWidth * imageInfo.originalRendition.height / imageInfo.originalRendition.width)
let indexPath = NSIndexPath(row: cellIndex, section: 0)
7 years ago
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
let itemFrame = CGRect(x: CGFloat(cellX), y: CGFloat(cellY), width: CGFloat(cellWidth), height: CGFloat(cellHeight))
itemAttributes.frame = itemFrame
itemAttributesMap[UInt(cellIndex)] = itemAttributes
columnYs[column] = cellY + cellHeight + vSpacing
contentBottom = max(contentBottom, cellY + cellHeight)
}
// Add bottom margin.
let contentHeight = contentBottom + vInset
7 years ago
contentSize = CGSize(width: CGFloat(totalViewWidth), height: CGFloat(contentHeight))
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return itemAttributesMap.values.filter { itemAttributes in
return itemAttributes.frame.intersects(rect)
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let result = itemAttributesMap[UInt(indexPath.row)]
return result
}
override var collectionViewContentSize: CGSize {
return contentSize
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView = collectionView else {
return false
}
return collectionView.bounds.width != newBounds.size.width
}
}