浏览代码

implemented grid layout

nayooti 5 年之前
父节点
当前提交
2d942ddce1

+ 8 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -130,6 +130,8 @@
 		7A9FB14E1FB061E2001FEA36 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A9FB14C1FB061E2001FEA36 /* LaunchScreen.storyboard */; };
 		7AE0A5491FC42F65005ECB4B /* NewChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE0A5481FC42F65005ECB4B /* NewChatViewController.swift */; };
 		8B6D425BC604F7C43B65D436 /* Pods_deltachat_ios.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6241BE1534A653E79AD5D01D /* Pods_deltachat_ios.framework */; };
+		AE0AA952247800E700D42A7F /* GalleryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE0AA951247800E700D42A7F /* GalleryCell.swift */; };
+		AE0AA9542478010D00D42A7F /* GallerySectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE0AA9532478010D00D42A7F /* GallerySectionHeader.swift */; };
 		AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE0D26FC1FB1FE88002FAFCE /* ChatListController.swift */; };
 		AE18F294228C602A0007B1BE /* SecuritySettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18F293228C602A0007B1BE /* SecuritySettingsController.swift */; };
 		AE19887523EB264000B4CD5F /* HelpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE19887423EB264000B4CD5F /* HelpViewController.swift */; };
@@ -410,6 +412,8 @@
 		8DE110C607A0E4485C43B5FA /* Pods-deltachat-ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-ios.debug.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.debug.xcconfig"; sourceTree = "<group>"; };
 		914DDA723E78965D83162E78 /* Pods-deltachat-ios-DcShare.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-ios-DcShare.debug.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-ios-DcShare/Pods-deltachat-ios-DcShare.debug.xcconfig"; sourceTree = "<group>"; };
 		A8615D4600859851E53CAA9C /* Pods-deltachat-ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-ios.release.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.release.xcconfig"; sourceTree = "<group>"; };
+		AE0AA951247800E700D42A7F /* GalleryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryCell.swift; sourceTree = "<group>"; };
+		AE0AA9532478010D00D42A7F /* GallerySectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GallerySectionHeader.swift; sourceTree = "<group>"; };
 		AE0D26FC1FB1FE88002FAFCE /* ChatListController.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; tabWidth = 4; };
 		AE18F293228C602A0007B1BE /* SecuritySettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecuritySettingsController.swift; sourceTree = "<group>"; };
 		AE19887423EB264000B4CD5F /* HelpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpViewController.swift; sourceTree = "<group>"; };
@@ -839,6 +843,7 @@
 			isa = PBXGroup;
 			children = (
 				AE406EEF240FF8FF005F7022 /* ProfileCell.swift */,
+				AE0AA951247800E700D42A7F /* GalleryCell.swift */,
 			);
 			path = Cell;
 			sourceTree = "<group>";
@@ -953,6 +958,7 @@
 				AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */,
 				AEB54C7E246DBA610004624C /* FlexLabel.swift */,
 				AED62BCD247687E6009E220D /* LocationStreamingIndicator.swift */,
+				AE0AA9532478010D00D42A7F /* GallerySectionHeader.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1340,6 +1346,7 @@
 				305961EA2346125100C80F33 /* MessageStyle.swift in Sources */,
 				305961F92346125100C80F33 /* MessageLabel.swift in Sources */,
 				304219D92440734A00516852 /* DcMsg+Extension.swift in Sources */,
+				AE0AA9542478010D00D42A7F /* GallerySectionHeader.swift in Sources */,
 				305961FA2346125100C80F33 /* MessageReusableView.swift in Sources */,
 				3040F462234F550300FA34D5 /* AudioPlayerView.swift in Sources */,
 				AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */,
@@ -1453,6 +1460,7 @@
 				7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */,
 				300C50A1234BDAB800F8AE22 /* TextMediaMessageSizeCalculator.swift in Sources */,
 				30F9B9EC235F2116006E7ACF /* MessageCounter.swift in Sources */,
+				AE0AA952247800E700D42A7F /* GalleryCell.swift in Sources */,
 				307D822E241669C7006D2490 /* LocationManager.swift in Sources */,
 				305961F12346125100C80F33 /* ContactMessageCell.swift in Sources */,
 				AE851AD0227DF50900ED86F0 /* GroupChatDetailViewController.swift in Sources */,

+ 110 - 62
deltachat-ios/Controller/GalleryViewController.swift

@@ -8,26 +8,36 @@ class GalleryViewController: UIViewController {
         let msgIds: [Int]
     }
 
+    // MARK: - data
     private let mediaMessageIds: [Int]
-
     private var gridSections: [GallerySection] = []
 
+    private lazy var gridLayout: GridCollectionViewFlowLayout = {
+        let layout = GridCollectionViewFlowLayout()
+        layout.minimumLineSpacing = 10
+        layout.minimumInteritemSpacing = 10
+        return layout
+    }()
+
+    // MARK: - subview specs
     private lazy var grid: UICollectionView = {
-        let layout = UICollectionViewFlowLayout()
-        layout.itemSize = CGSize(width: 50, height: 50)
-        let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        let collection = UICollectionView(frame: .zero, collectionViewLayout: gridLayout)
         collection.dataSource = self
         collection.delegate = self
-        collection.register(MediaCell.self, forCellWithReuseIdentifier: MediaCell.reuseIdentifier)
+        collection.register(GalleryCell.self, forCellWithReuseIdentifier: GalleryCell.reuseIdentifier)
         collection.register(
             GalleryGridSectionHeader.self,
             forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
             withReuseIdentifier: GalleryGridSectionHeader.reuseIdentifier
         )
+        collection.contentInset = UIEdgeInsets(top: 0, left: gridInsets, bottom: 0, right: gridInsets)
         collection.backgroundColor = .white
         return collection
     }()
 
+    // MARK: - specs
+    private let gridInsets: CGFloat = 10
+
     init(mediaMessageIds: [Int]) {
         self.mediaMessageIds = mediaMessageIds
         super.init(nibName: nil, bundle: nil)
@@ -48,14 +58,19 @@ class GalleryViewController: UIViewController {
         grid.reloadData()
     }
 
+    override func viewWillLayoutSubviews() {
+        super.viewWillLayoutSubviews()
+        self.reloadCollectionViewLayout()
+    }
+
     // MARK: - setup
     private func setupSubviews() {
         view.addSubview(grid)
         grid.translatesAutoresizingMaskIntoConstraints = false
 
-        grid.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+        grid.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
         grid.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
-        grid.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
+        grid.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
         grid.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
     }
 
@@ -78,7 +93,7 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
     }
 
     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
-        let mediaCell = collectionView.dequeueReusableCell(withReuseIdentifier: MediaCell.reuseIdentifier, for: indexPath) as! MediaCell
+        let mediaCell = collectionView.dequeueReusableCell(withReuseIdentifier: GalleryCell.reuseIdentifier, for: indexPath) as! GalleryCell
         let msg = DcMsg(id: mediaMessageIds[indexPath.row])
         mediaCell.update(msg: msg)
         // cell update
@@ -92,86 +107,119 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
             for: indexPath
             ) as? GalleryGridSectionHeader {
             header.text = gridSections[indexPath.section].headerTitle
+            header.leadingMargin = gridInsets // to have grid and header equally aligned
             return header
         }
         return UICollectionReusableView()
     }
 
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
-        return CGSize(width: collectionView.frame.width, height: 40)
+        return CGSize(width: collectionView.frame.width - 2 * gridInsets, height: 32)
     }
 }
 
-class MediaCell: UICollectionViewCell {
-    static let reuseIdentifier = "media_cell"
-
-    var imageView: UIImageView = {
-        let view = UIImageView()
-        view.contentMode = .scaleAspectFill
-        view.clipsToBounds = true
-        return view
-    }()
+extension GalleryViewController {
+    private func reloadCollectionViewLayout() {
+
+        // columns specification
+        let phonePortrait = 2
+        let phoneLandscape = 3
+        let padPortrait = 3
+        let padLandscape = 5
+
+        let orientation = UIDevice.current.orientation
+        let deviceType = UIDevice.current.userInterfaceIdiom
+
+
+        var gridDisplay: CollectionDisplay?
+        if deviceType == .phone {
+            if orientation.isPortrait {
+                gridDisplay = .grid(columns: phonePortrait)
+            } else {
+                gridDisplay = .grid(columns: phoneLandscape)
+            }
+        } else if deviceType == .pad {
+            if orientation.isPortrait {
+                gridDisplay = .grid(columns: padPortrait)
+            } else {
+                gridDisplay = .grid(columns: padLandscape)
+            }
+        }
 
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        setupSubviews()
+        if let gridDisplay = gridDisplay {
+            gridLayout.display = gridDisplay
+        } else {
+            safe_fatalError("undefined format")
+        }
+        let containerWidth = view.bounds.width - view.safeAreaInsets.left - view.safeAreaInsets.right - 2 * gridInsets
+        gridLayout.containerWidth = containerWidth
     }
+ }
 
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
 
-    private func setupSubviews() {
-        contentView.addSubview(imageView)
-        imageView.translatesAutoresizingMaskIntoConstraints = false
-        imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
-        imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
-        imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
-        imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
-    }
+enum CollectionDisplay {
+    case list
+    case grid(columns: Int)
+}
+
+extension CollectionDisplay: Equatable {
+
+    public static func == (lhs: CollectionDisplay, rhs: CollectionDisplay) -> Bool {
+
+        switch (lhs, rhs) {
+        case (.list, .list):
+            return true
+        case (.grid(let lColumn), .grid(let rColumn)):
+            return lColumn == rColumn
 
-    func update(msg: DcMsg) {
-        guard let image = msg.image else {
-            return
+        default:
+            return false
         }
-        imageView.image = image
     }
 }
 
-class GalleryGridSectionHeader: UICollectionReusableView {
-    static let reuseIdentifier = "gallery_grid_section_header"
+class GridCollectionViewFlowLayout: UICollectionViewFlowLayout {
 
-    private lazy var label: UILabel = {
-        let label = UILabel()
-        label.textColor = DcColors.grayDateColor
-        return label
-    }()
-
-    var text: String? {
-        set {
-            label.text = newValue?.uppercased()
+    var display: CollectionDisplay = .list {
+        didSet {
+            if display != oldValue {
+                self.invalidateLayout()
+            }
         }
-        get {
-            return label.text
+    }
+
+    var containerWidth: CGFloat = 0.0 {
+        didSet {
+            if containerWidth != oldValue {
+                self.invalidateLayout()
+            }
         }
     }
 
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        setupSubviews()
-        backgroundColor = .white
+    convenience init(display: CollectionDisplay, containerWidth: CGFloat) {
+        self.init()
+        self.display = display
+        self.containerWidth = containerWidth
+        self.configLayout()
     }
 
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
+    private func configLayout() {
+        switch display {
+        case .grid(let column):
+            self.scrollDirection = .vertical
+            let spacing = CGFloat(column - 1) * minimumLineSpacing
+            let optimisedWidth = (containerWidth - spacing) / CGFloat(column)
+            self.itemSize = CGSize(width: optimisedWidth, height: optimisedWidth) // keep as square
+        case .list:
+            self.scrollDirection = .vertical
+            self.itemSize = CGSize(width: containerWidth, height: containerWidth)
+        }
     }
 
-    private func setupSubviews() {
-        addSubview(label)
-        label.translatesAutoresizingMaskIntoConstraints = false
-        label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0).isActive = true
-        label.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
-        label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0).isActive = true
-        label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
+    override func invalidateLayout() {
+        super.invalidateLayout()
+        self.configLayout()
     }
 }
+
+

+ 38 - 0
deltachat-ios/View/Cell/GalleryCell.swift

@@ -0,0 +1,38 @@
+import UIKit
+import DcCore
+
+class GalleryCell: UICollectionViewCell {
+    static let reuseIdentifier = "gallery_cell"
+
+    var imageView: UIImageView = {
+        let view = UIImageView()
+        view.contentMode = .scaleAspectFill
+        view.clipsToBounds = true
+        return view
+    }()
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func setupSubviews() {
+        contentView.addSubview(imageView)
+        imageView.translatesAutoresizingMaskIntoConstraints = false
+        imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
+        imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
+        imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
+        imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
+    }
+
+    func update(msg: DcMsg) {
+        guard let image = msg.image else {
+            return
+        }
+        imageView.image = image
+    }
+}

+ 46 - 0
deltachat-ios/View/GallerySectionHeader.swift

@@ -0,0 +1,46 @@
+import UIKit
+import DcCore
+
+class GalleryGridSectionHeader: UICollectionReusableView {
+    static let reuseIdentifier = "gallery_grid_section_header"
+
+    private lazy var label: UILabel = {
+        let label = UILabel()
+        label.textColor = DcColors.grayTextColor
+        return label
+    }()
+
+    var leadingMargin: CGFloat = 0 {
+        didSet {
+            setNeedsLayout()
+        }
+    }
+
+    var text: String? {
+        set {
+            label.text = newValue?.uppercased()
+        }
+        get {
+            return label.text
+        }
+    }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+        backgroundColor = .white
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func setupSubviews() {
+        addSubview(label)
+        label.translatesAutoresizingMaskIntoConstraints = false
+        label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: leadingMargin).isActive = true
+        label.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
+        label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -leadingMargin).isActive = true
+        label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
+    }
+}