Fix sticker keyboard layout.

This commit is contained in:
Matthew Chen 2019-04-24 16:38:45 -04:00
parent 4708963ebd
commit 48df37519f
12 changed files with 285 additions and 10 deletions

View File

@ -274,6 +274,7 @@
34C2EEB02270B8E200BCA1D0 /* StickerKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEAF2270B8E100BCA1D0 /* StickerKeyboard.swift */; };
34C2EEB22270CC8E00BCA1D0 /* StickerPackCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */; };
34C2EEB42270D1CE00BCA1D0 /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB32270D1CE00BCA1D0 /* StickerView.swift */; };
34C2EEB62270FF7C00BCA1D0 /* LinearHorizontalLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */; };
34C3C78D20409F320000134C /* Opening.m4r in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78C20409F320000134C /* Opening.m4r */; };
34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78E2040A4F70000134C /* sonarping.mp3 */; };
34C3C7922040B0DD0000134C /* OWSAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -985,6 +986,7 @@
34C2EEAF2270B8E100BCA1D0 /* StickerKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerKeyboard.swift; sourceTree = "<group>"; };
34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackCollectionView.swift; sourceTree = "<group>"; };
34C2EEB32270D1CE00BCA1D0 /* StickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerView.swift; sourceTree = "<group>"; };
34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearHorizontalLayout.swift; sourceTree = "<group>"; };
34C3C78C20409F320000134C /* Opening.m4r */ = {isa = PBXFileReference; lastKnownFileType = file; path = Opening.m4r; sourceTree = "<group>"; };
34C3C78E2040A4F70000134C /* sonarping.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = sonarping.mp3; path = Signal/AudioFiles/sonarping.mp3; sourceTree = SOURCE_ROOT; };
34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAudioPlayer.h; sourceTree = "<group>"; };
@ -1726,6 +1728,7 @@
344DC9AD226E483C004E7322 /* Stickers */ = {
isa = PBXGroup;
children = (
34C2EEB52270FF7B00BCA1D0 /* LinearHorizontalLayout.swift */,
344DC9AE226E483C004E7322 /* ManageStickersViewController.swift */,
34C2EEAF2270B8E100BCA1D0 /* StickerKeyboard.swift */,
34C2EEB12270CC8D00BCA1D0 /* StickerPackCollectionView.swift */,
@ -3484,6 +3487,7 @@
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */,
45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */,
34BEDB0E21C405B0007B0EAE /* ImageEditorModel.swift in Sources */,
34C2EEB62270FF7C00BCA1D0 /* LinearHorizontalLayout.swift in Sources */,
451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */,
340872C122394CAA00CB25B0 /* ImageEditorTransform.swift in Sources */,
450C800F20AD1AB900F3A091 /* OWSWindowManager.m in Sources */,

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "plus-24@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "plus-24@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "plus-24@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "search-24@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "search-24@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "search-24@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

View File

@ -0,0 +1,104 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
// A trivial layout that places each item in a horizontal line.
// Each item has uniform size.
class LinearHorizontalLayout: UICollectionViewLayout {
private let itemSize: CGSize
private let inset: CGFloat
private let spacing: CGFloat
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) {
notImplemented()
}
required init(itemSize: CGSize, inset: CGFloat = 0, spacing: CGFloat = 0) {
self.itemSize = itemSize
self.inset = inset
self.spacing = spacing
super.init()
}
// MARK: Methods
override func invalidateLayout() {
super.invalidateLayout()
itemAttributesMap.removeAll()
}
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
super.invalidateLayout(with: context)
itemAttributesMap.removeAll()
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else {
return
}
guard collectionView.numberOfSections == 1 else {
owsFailDebug("This layout only support a single section.")
return
}
let itemCount = collectionView.numberOfItems(inSection: 0)
let vInset: CGFloat = inset
let hInset: CGFloat = inset
guard itemCount > 0 else {
contentSize = .zero
return
}
for row in 0..<itemCount {
let itemX: CGFloat = hInset + CGFloat(row) * (itemSize.width + spacing)
let itemY: CGFloat = vInset
let itemFrame = CGRect(x: itemX, y: itemY, width: itemSize.width, height: itemSize.height)
let indexPath = NSIndexPath(row: row, section: 0)
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
itemAttributes.frame = itemFrame
itemAttributesMap[UInt(row)] = itemAttributes
}
contentSize = CGSize(width: hInset * 2 + CGFloat(itemCount) * itemSize.width + CGFloat(itemCount - 1) * spacing,
height: vInset * 2 + itemSize.height)
}
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.width() != newBounds.size.width
}
}

View File

@ -43,8 +43,12 @@ public class StickerKeyboard: UIStackView {
object: nil)
}
required public init(coder: NSCoder) {
notImplemented()
}
// TODO: Tune this value.
private let keyboardHeight: CGFloat = 200
private let keyboardHeight: CGFloat = 300
@objc
public override var intrinsicContentSize: CGSize {
@ -59,31 +63,81 @@ public class StickerKeyboard: UIStackView {
addBackgroundView(withBackgroundColor: Theme.offBackgroundColor)
headerView.axis = .horizontal
addArrangedSubview(headerView)
headerView.setContentHuggingVerticalHigh()
headerView.setCompressionResistanceVerticalHigh()
headerView.autoSetDimension(.height, toSize: 44)
stickerCollectionView.stickerDelegate = self
addArrangedSubview(stickerCollectionView)
stickerCollectionView.setContentHuggingVerticalLow()
stickerCollectionView.setCompressionResistanceVerticalLow()
populateHeaderView()
}
private func reloadStickers() {
stickerPacks = StickerManager.installedStickerPacks()
packsCollectionView.collectionViewLayout.invalidateLayout()
packsCollectionView.reloadData()
guard stickerPacks.count > 0 else {
stickerPack = nil
stickerPack = nil
return
}
if stickerPack == nil {
stickerPack = stickerPacks.first
}
}
// TODO: Reload header?
private let packsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: buildCoverLayout())
private let cellReuseIdentifier = "cellReuseIdentifier"
private static let packCoverSize: CGFloat = 24
private static let packCoverSpacing: CGFloat = 12
private class func buildCoverLayout() -> UICollectionViewLayout {
return LinearHorizontalLayout(itemSize: CGSize(width: packCoverSize, height: packCoverSize), inset: 0, spacing: packCoverSpacing)
}
private func populateHeaderView() {
headerView.spacing = StickerKeyboard.packCoverSpacing
headerView.axis = .horizontal
headerView.alignment = .center
headerView.backgroundColor = Theme.offBackgroundColor
headerView.layoutMargins = UIEdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)
headerView.isLayoutMarginsRelativeArrangement = true
let searchButton = OWSButton(imageName: "search-24", tintColor: Theme.secondaryColor) { [weak self] in
self?.searchButtonWasTapped()
}
searchButton.setContentHuggingHigh()
searchButton.setCompressionResistanceHigh()
headerView.addArrangedSubview(searchButton)
packsCollectionView.backgroundColor = Theme.offBackgroundColor
packsCollectionView.delegate = self
packsCollectionView.dataSource = self
packsCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
backgroundColor = Theme.offBackgroundColor
packsCollectionView.setContentHuggingHorizontalLow()
packsCollectionView.setCompressionResistanceHorizontalLow()
packsCollectionView.autoSetDimension(.height, toSize: StickerKeyboard.packCoverSize)
headerView.addArrangedSubview(packsCollectionView)
let manageButton = OWSButton(imageName: "plus-24", tintColor: Theme.secondaryColor) { [weak self] in
self?.manageButtonWasTapped()
}
manageButton.setContentHuggingHigh()
manageButton.setCompressionResistanceHigh()
headerView.addArrangedSubview(manageButton)
updateHeaderView()
}
private func updateHeaderView() {
}
// MARK: Events
@ -94,10 +148,23 @@ public class StickerKeyboard: UIStackView {
Logger.verbose("")
reloadStickers()
updateHeaderView()
}
required public init(coder: NSCoder) {
notImplemented()
private func searchButtonWasTapped() {
AssertIsOnMainThread()
Logger.verbose("")
// TODO:
}
private func manageButtonWasTapped() {
AssertIsOnMainThread()
Logger.verbose("")
// TODO:
}
}
@ -112,3 +179,51 @@ extension StickerKeyboard: StickerPackCollectionViewDelegate {
delegate?.didSelectSticker(stickerInfo: stickerInfo)
}
}
// MARK: - UICollectionViewDelegate
extension StickerKeyboard: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
Logger.debug("")
guard let stickerPack = stickerPacks[safe: indexPath.row] else {
owsFailDebug("Invalid index path: \(indexPath)")
return
}
self.stickerPack = stickerPack
}
}
// MARK: - UICollectionViewDataSource
extension StickerKeyboard: UICollectionViewDataSource {
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection sectionIdx: Int) -> Int {
return stickerPacks.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// We could eventually use cells that lazy-load the sticker views
// when the cells becomes visible and eagerly unload them.
// But we probably won't need to do that.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath)
guard let stickerPack = stickerPacks[safe: indexPath.row] else {
owsFailDebug("Invalid index path: \(indexPath)")
return cell
}
// TODO: Actual size?
let iconView = StickerView(stickerInfo: stickerPack.coverInfo)
cell.contentView.addSubview(iconView)
iconView.autoPinEdgesToSuperviewEdges()
return cell
}
}

View File

@ -22,6 +22,8 @@ public class StickerPackCollectionView: UICollectionView {
AssertIsOnMainThread()
reloadStickers()
// Scroll to the top.
contentOffset = .zero
}
}
@ -151,6 +153,8 @@ extension StickerPackCollectionView {
}
layout.minimumInteritemSpacing = kSpacing
layout.minimumLineSpacing = kSpacing
let inset = kSpacing
layout.sectionInset = UIEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
return layout
}
@ -170,9 +174,11 @@ extension StickerPackCollectionView {
}
let spacing = StickerPackCollectionView.kSpacing
let preferredCellSize: CGFloat = 84
let columnCount = UInt((containerWidth + spacing) / (preferredCellSize + spacing))
let cellWidth = (containerWidth - spacing * (CGFloat(columnCount) - 1)) / CGFloat(columnCount)
let inset = spacing
let preferredCellSize: CGFloat = 80
let contentWidth = containerWidth - 2 * inset
let columnCount = UInt((contentWidth + spacing) / (preferredCellSize + spacing))
let cellWidth = (contentWidth - spacing * (CGFloat(columnCount) - 1)) / CGFloat(columnCount)
let itemSize = CGSize(width: cellWidth, height: cellWidth)
if (itemSize != flowLayout.itemSize) {