Merge branch 'develop' into feature/tags_items_view
* develop: reuse add check for single hiding pager Add action to collection cells make paging configurable for carousel. add insets to carousel use button helper function for collection and table cell. allow server override move model update set model as well fix to anchor remove images from core ui Add line. Fix for defect CXTDT-83977: rightLabel not aligned with headline. Tabs update Setup listener for tab response view will appear tab change thread protection fix to bottom anchor tab bar selection logic change bar button item to convenience function Tab updates Demo
This commit is contained in:
commit
c9775376d3
@ -337,6 +337,10 @@
|
||||
D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */; };
|
||||
D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */; };
|
||||
D28BA730247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA72F247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift */; };
|
||||
D28BA741248025A300B75CB8 /* TabBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA740248025A300B75CB8 /* TabBarModel.swift */; };
|
||||
D28BA7432480284E00B75CB8 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA7422480284E00B75CB8 /* TabBar.swift */; };
|
||||
D28BA7452481652D00B75CB8 /* TabBarProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */; };
|
||||
D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */; };
|
||||
D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */; };
|
||||
D29C94D5242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */; };
|
||||
@ -774,6 +778,10 @@
|
||||
D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabelsModel.swift; sourceTree = "<group>"; };
|
||||
D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyCaretLinkImageModel.swift; sourceTree = "<group>"; };
|
||||
D28BA72F247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtomModelProtocol.swift; sourceTree = "<group>"; };
|
||||
D28BA740248025A300B75CB8 /* TabBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarModel.swift; sourceTree = "<group>"; };
|
||||
D28BA7422480284E00B75CB8 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
|
||||
D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarProtocol.swift; sourceTree = "<group>"; };
|
||||
D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPageModelProtocol.swift; sourceTree = "<group>"; };
|
||||
D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewConstrainingProtocol.h; sourceTree = "<group>"; };
|
||||
D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleMolecule.swift; sourceTree = "<group>"; };
|
||||
D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUICommonViewsUtility+Extension.swift"; sourceTree = "<group>"; };
|
||||
@ -910,6 +918,7 @@
|
||||
D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */,
|
||||
D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */,
|
||||
D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */,
|
||||
D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */,
|
||||
);
|
||||
path = ModelProtocols;
|
||||
sourceTree = "<group>";
|
||||
@ -1235,6 +1244,8 @@
|
||||
017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */,
|
||||
D28764FA245A33A500CB882D /* TwoLinkViewModel.swift */,
|
||||
D28764F8245A327200CB882D /* TwoLinkView.swift */,
|
||||
D28BA740248025A300B75CB8 /* TabBarModel.swift */,
|
||||
D28BA7422480284E00B75CB8 /* TabBar.swift */,
|
||||
);
|
||||
path = HorizontalCombinationViews;
|
||||
sourceTree = "<group>";
|
||||
@ -1892,6 +1903,7 @@
|
||||
012A88C7238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift */,
|
||||
017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */,
|
||||
012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */,
|
||||
D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */,
|
||||
011B58EE23A2AA850085F53C /* ModelProtocols */,
|
||||
);
|
||||
path = Protocols;
|
||||
@ -2042,6 +2054,7 @@
|
||||
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
|
||||
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
|
||||
D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */,
|
||||
D28BA7452481652D00B75CB8 /* TabBarProtocol.swift in Sources */,
|
||||
AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */,
|
||||
D28764AA2458980300CB882D /* ThreeLayerFillMiddleTemplate.swift in Sources */,
|
||||
0116A4E5228B19640094F3ED /* RadioButtonSelectionHelper.swift in Sources */,
|
||||
@ -2109,6 +2122,7 @@
|
||||
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */,
|
||||
D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */,
|
||||
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */,
|
||||
D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */,
|
||||
014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */,
|
||||
0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */,
|
||||
BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */,
|
||||
@ -2287,6 +2301,7 @@
|
||||
D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */,
|
||||
AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */,
|
||||
DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */,
|
||||
D28BA741248025A300B75CB8 /* TabBarModel.swift in Sources */,
|
||||
D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */,
|
||||
D2A92882241AAB67004E01C6 /* ScrollingViewController.swift in Sources */,
|
||||
C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */,
|
||||
@ -2387,6 +2402,7 @@
|
||||
0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */,
|
||||
D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */,
|
||||
94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */,
|
||||
D28BA7432480284E00B75CB8 /* TabBar.swift in Sources */,
|
||||
AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */,
|
||||
011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */,
|
||||
D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */,
|
||||
|
||||
@ -206,6 +206,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
isEnabled = model.enabled
|
||||
|
||||
formatAccessibilityValue(index: currentIndex + 1, total: numberOfPages)
|
||||
isHidden = model.hidesForSinglePage && numberOfPages <= 1
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -33,7 +33,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
||||
public var disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3)
|
||||
public var indicatorColor: Color = Color(uiColor: .mvmBlack)
|
||||
public var indicatorColor_inverted: Color = Color(uiColor: .mvmWhite)
|
||||
public var position: Float?
|
||||
public var position: CGFloat?
|
||||
|
||||
/// Allows sendActions() to trigger even if index is already at min/max index.
|
||||
public var alwaysSendAction = false
|
||||
@ -79,7 +79,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
||||
self.inverted = inverted
|
||||
}
|
||||
|
||||
if let position = try typeContainer.decodeIfPresent(Float.self, forKey: .position) {
|
||||
if let position = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .position) {
|
||||
self.position = position
|
||||
}
|
||||
|
||||
|
||||
@ -14,15 +14,17 @@ import Foundation
|
||||
public let leftImage = LoadImageView()
|
||||
public let headlineBody = HeadlineBody()
|
||||
public let rightLabel = Label.createLabelRegularBodySmall(true)
|
||||
public let rightLabelStackItem: StackItem
|
||||
public var stack: Stack<StackModel>
|
||||
|
||||
//-----------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//-----------------------------------------------------
|
||||
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
stack = Stack<StackModel>.createStack(with: [(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)),
|
||||
(view: headlineBody, model: StackItemModel(horizontalAlignment: .leading)),
|
||||
(view: rightLabel, model: StackItemModel(horizontalAlignment: .fill, verticalAlignment: .leading))], axis: .horizontal)
|
||||
rightLabelStackItem = StackItem(andContain: rightLabel)
|
||||
let stackItems = [StackItem(andContain: leftImage), StackItem(andContain: headlineBody), rightLabelStackItem]
|
||||
let stackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .fill), StackItemModel(horizontalAlignment: .fill), StackItemModel(horizontalAlignment: .fill)], axis: .horizontal)
|
||||
stack = Stack<StackModel>(with: stackModel, stackItems: stackItems)
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
}
|
||||
|
||||
@ -30,6 +32,16 @@ import Foundation
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open override func alignAccessoryToHero() -> CGPoint? {
|
||||
// Ensures that the right label is centered vertically with headline.
|
||||
let heroCenter = super.alignAccessoryToHero()
|
||||
if let heroCenter = heroCenter {
|
||||
let convertedPoint = stack.convert(heroCenter, from: self)
|
||||
rightLabelStackItem.containerHelper.alignCenterVerticalConstraint?.constant = convertedPoint.y - stack.bounds.midY
|
||||
}
|
||||
return heroCenter
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
// MARK: - View Lifecycle
|
||||
//-------------------------------------------------------
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
//
|
||||
// TabBar.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 5/28/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objcMembers open class TabBar: UITabBar, MoleculeViewProtocol, TabBarProtocol, UITabBarDelegate {
|
||||
|
||||
public var model: TabBarModel
|
||||
public var delegateObject: MVMCoreUIDelegateObject?
|
||||
public let line = Line()
|
||||
|
||||
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
guard let model = model as? TabBarModel else {
|
||||
fatalError("model is not TabBarModel")
|
||||
}
|
||||
self.model = model
|
||||
super.init(frame: .zero)
|
||||
|
||||
delegate = self
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
line.addLine(to: self, edge: .top, useMargin: false)
|
||||
line.backgroundColor = .mvmCoolGray3
|
||||
set(with: model, delegateObject, additionalData)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
||||
guard let model = model as? TabBarModel else { return }
|
||||
self.model = model
|
||||
|
||||
// Set appearance
|
||||
if #available(iOS 13.0, *) {
|
||||
let appearance = UITabBarAppearance()
|
||||
appearance.backgroundColor = model.backgroundColor?.uiColor
|
||||
setTabBarItemColors(appearance.stackedLayoutAppearance, model: model)
|
||||
setTabBarItemColors(appearance.inlineLayoutAppearance, model: model)
|
||||
setTabBarItemColors(appearance.compactInlineLayoutAppearance, model: model)
|
||||
standardAppearance = appearance
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
backgroundColor = model.backgroundColor?.uiColor
|
||||
tintColor = model.selectedColor.uiColor
|
||||
unselectedItemTintColor = model.unSelectedColor.uiColor
|
||||
barTintColor = model.backgroundColor?.uiColor
|
||||
isTranslucent = false
|
||||
}
|
||||
|
||||
// Add buttons
|
||||
var tabs: [UITabBarItem] = []
|
||||
for (index, tab) in model.tabs.enumerated() {
|
||||
let tabBarItem = UITabBarItem(title: tab.title, image: UIImage(named: tab.image, in: MVMCoreCache.shared()?.bundleToUseForImages(), compatibleWith: nil), tag: index)
|
||||
tabs.append(tabBarItem)
|
||||
}
|
||||
setItems(tabs, animated: false)
|
||||
selectedItem = tabs[model.selectedTab]
|
||||
}
|
||||
|
||||
/// Sets the item colors.
|
||||
@available(iOS 13.0, *)
|
||||
private func setTabBarItemColors(_ itemAppearance: UITabBarItemAppearance, model: TabBarModel) {
|
||||
itemAppearance.normal.iconColor = model.unSelectedColor.uiColor
|
||||
itemAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: model.unSelectedColor.uiColor]
|
||||
|
||||
itemAppearance.selected.iconColor = model.selectedColor.uiColor
|
||||
itemAppearance.selected.titleTextAttributes = [NSAttributedString.Key.foregroundColor: model.selectedColor.uiColor]
|
||||
}
|
||||
|
||||
// MARK: - UITabBarDelegate
|
||||
public func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||
self.model.selectedTab = item.tag
|
||||
Button.performButtonAction(with: model.tabs[item.tag].action, button: item, delegateObject: delegateObject, additionalData: nil)
|
||||
}
|
||||
|
||||
// MARK: - TabBarProtocol
|
||||
public func highlightTab(at index: Int) {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let newSelectedItem = self.items?[index] else { return }
|
||||
self.model.selectedTab = index
|
||||
self.selectedItem = newSelectedItem
|
||||
})
|
||||
}
|
||||
|
||||
public func selectTab(at index: Int) {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let newSelectedItem = self.items?[index] else { return }
|
||||
self.selectedItem = newSelectedItem
|
||||
self.tabBar(self, didSelect: newSelectedItem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension UITabBarItem: MFButtonProtocol {
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
//
|
||||
// TabBarModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 5/28/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class TabBarModel: MoleculeModelProtocol {
|
||||
public static var identifier: String = "tabBar"
|
||||
public var backgroundColor: Color? = Color(uiColor: .white)
|
||||
public var tabs: [TabBarItemModel]
|
||||
public var selectedColor = Color(uiColor: .mvmBlack)
|
||||
public var unSelectedColor = Color(uiColor: .mvmCoolGray3)
|
||||
|
||||
// Must be capped to 0...(tabs.count - 1)
|
||||
public var selectedTab: Int = 0
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case moleculeName
|
||||
case backgroundColor
|
||||
case tabs
|
||||
case selectedColor
|
||||
case unSelectedColor
|
||||
case selectedTab
|
||||
}
|
||||
|
||||
public init(with tabs: [TabBarItemModel]) {
|
||||
self.tabs = tabs
|
||||
}
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
tabs = try typeContainer.decode([TabBarItemModel].self, forKey: .tabs)
|
||||
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) {
|
||||
backgroundColor = color
|
||||
}
|
||||
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .unSelectedColor) {
|
||||
unSelectedColor = color
|
||||
}
|
||||
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor) {
|
||||
selectedColor = color
|
||||
}
|
||||
if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedTab) {
|
||||
selectedTab = index
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(moleculeName, forKey: .moleculeName)
|
||||
try container.encode(tabs, forKey: .tabs)
|
||||
try container.encode(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encode(selectedColor, forKey: .selectedColor)
|
||||
try container.encode(unSelectedColor, forKey: .unSelectedColor)
|
||||
try container.encode(selectedTab, forKey: .selectedTab)
|
||||
}
|
||||
}
|
||||
|
||||
public class TabBarItemModel: Codable {
|
||||
var title: String
|
||||
var image: String
|
||||
var action: ActionModelProtocol
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case title
|
||||
case image
|
||||
case action
|
||||
}
|
||||
|
||||
public init(with title: String, image: String, action: ActionModelProtocol) {
|
||||
self.title = title
|
||||
self.image = image
|
||||
self.action = action
|
||||
}
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
title = try typeContainer.decode(String.self, forKey: .title)
|
||||
image = try typeContainer.decode(String.self, forKey: .image)
|
||||
action = try typeContainer.decodeModel(codingKey: .action)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(title, forKey: .title)
|
||||
try container.encode(image, forKey: .image)
|
||||
try container.encodeModel(action, forKey: .action)
|
||||
}
|
||||
}
|
||||
@ -78,5 +78,4 @@ public class TabItemModel: Codable {
|
||||
try container.encodeModel(label, forKey: .label)
|
||||
try container.encodeModelIfPresent(action, forKey: .action)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -13,6 +13,12 @@ import Foundation
|
||||
open override class var identifier: String {
|
||||
return "collectionItem"
|
||||
}
|
||||
|
||||
public var action: ActionModelProtocol?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case action
|
||||
}
|
||||
|
||||
/// Defaults to set
|
||||
public override func setDefaults() {
|
||||
@ -35,10 +41,14 @@ import Foundation
|
||||
}
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
|
||||
try super.init(from: decoder)
|
||||
}
|
||||
|
||||
public override func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeModelIfPresent(action, forKey: .action)
|
||||
try super.encode(to: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,8 +19,14 @@ public protocol CarouselPageControlProtocol {
|
||||
|
||||
open class Carousel: View {
|
||||
|
||||
public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
||||
|
||||
public let collectionView: CollectionView = {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
layout.minimumInteritemSpacing = 0
|
||||
layout.minimumLineSpacing = 0
|
||||
return CollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
}()
|
||||
|
||||
/// The current index of the collection view. Includes dummy cells when looping.
|
||||
public var currentIndex = 0
|
||||
|
||||
@ -36,13 +42,13 @@ open class Carousel: View {
|
||||
open var numberOfPages = 0
|
||||
|
||||
/// The models for the molecules.
|
||||
var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]?
|
||||
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]?
|
||||
|
||||
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
|
||||
public var itemAlignment = UICollectionView.ScrollPosition.left
|
||||
|
||||
/// From 0-1. The item width as a percent of the carousel width.
|
||||
public var itemWidthPercent: Float = 1
|
||||
public var itemWidthPercent: CGFloat = 1
|
||||
|
||||
/// The height of the carousel. Default is 300.
|
||||
public var collectionViewHeight: NSLayoutConstraint?
|
||||
@ -51,7 +57,7 @@ open class Carousel: View {
|
||||
public var pagingView: (UIView & CarouselPageControlProtocol)?
|
||||
|
||||
/// If the carousel should loop after scrolling past the first and final cells.
|
||||
var loop = false
|
||||
public var loop = false
|
||||
|
||||
private var dragging = false
|
||||
|
||||
@ -81,6 +87,8 @@ open class Carousel: View {
|
||||
showPeaking(false)
|
||||
|
||||
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
||||
guard let model = model as? CarouselModel,
|
||||
(model.paging == true || model.loop == true) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
@ -98,15 +106,23 @@ open class Carousel: View {
|
||||
collectionView.delegate = self
|
||||
addSubview(collectionView)
|
||||
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
|
||||
|
||||
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
||||
collectionViewHeight?.isActive = false
|
||||
collectionViewHeight?.isActive = true
|
||||
}
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
self.size = size
|
||||
|
||||
// Set insets for the carousel.
|
||||
var inset = UIEdgeInsets.zero
|
||||
let carouselModel = model as? CarouselModel
|
||||
if carouselModel?.useHorizontalMargins ?? false {
|
||||
inset.left = carouselModel?.leftPadding ?? Padding.Component.horizontalPaddingForSize(size)
|
||||
inset.right = carouselModel?.rightPadding ?? Padding.Component.horizontalPaddingForSize(size)
|
||||
}
|
||||
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset = inset
|
||||
|
||||
// Update cells and re-layout.
|
||||
for cell in collectionView.visibleCells {
|
||||
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
||||
@ -128,19 +144,19 @@ open class Carousel: View {
|
||||
collectionView.layer.borderColor = backgroundColor?.cgColor
|
||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||
backgroundColor = .white
|
||||
|
||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||
setupLayout(with: carouselModel)
|
||||
prepareMolecules(with: carouselModel)
|
||||
itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100
|
||||
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0
|
||||
|
||||
itemWidthPercent = carouselModel.itemWidthPercent / 100.0
|
||||
if let alignment = carouselModel.itemAlignment {
|
||||
itemAlignment = alignment
|
||||
}
|
||||
|
||||
if let height = carouselModel.height {
|
||||
collectionViewHeight?.constant = CGFloat(height)
|
||||
collectionViewHeight?.isActive = true
|
||||
collectionViewHeight?.constant = height
|
||||
}
|
||||
|
||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||
prepareMolecules(with: carouselModel)
|
||||
|
||||
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
||||
|
||||
@ -153,16 +169,6 @@ open class Carousel: View {
|
||||
// MARK: - JSON Setters
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Updates the layout being used
|
||||
func setupLayout(with carouselModel: CarouselModel?) {
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
layout.minimumLineSpacing = CGFloat(carouselModel?.spacing ?? 1)
|
||||
layout.minimumInteritemSpacing = 0
|
||||
collectionView.collectionViewLayout = layout
|
||||
}
|
||||
|
||||
func prepareMolecules(with carouselModel: CarouselModel?) {
|
||||
guard let newMolecules = carouselModel?.molecules else {
|
||||
numberOfPages = 0
|
||||
@ -187,11 +193,12 @@ open class Carousel: View {
|
||||
/// Sets up the paging molecule
|
||||
open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
var pagingView: (UIView & CarouselPageControlProtocol)? = nil
|
||||
if let molecule = molecule {
|
||||
if let molecule = molecule,
|
||||
(!molecule.hidesForSinglePage || numberOfPages > 1) {
|
||||
pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & CarouselPageControlProtocol)
|
||||
}
|
||||
|
||||
addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20)))
|
||||
addPaging(view: pagingView, position: molecule?.position ?? 20)
|
||||
}
|
||||
|
||||
/// Registers the cells with the collection view
|
||||
@ -294,7 +301,7 @@ open class Carousel: View {
|
||||
|
||||
extension Carousel: UICollectionViewDelegateFlowLayout {
|
||||
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||
return CGSize(width: itemWidth, height: collectionView.bounds.height)
|
||||
}
|
||||
|
||||
@ -324,8 +331,15 @@ extension Carousel: UICollectionViewDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
extension Carousel: UICollectionViewDelegate {
|
||||
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObject, additionalData: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension Carousel: UIScrollViewDelegate {
|
||||
|
||||
/// Go to the cell at the specified index.
|
||||
func goTo(_ index: Int, animated: Bool) {
|
||||
|
||||
showPeaking(false)
|
||||
@ -339,51 +353,33 @@ extension Carousel: UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserOnBufferCell() {
|
||||
guard loop else { return }
|
||||
|
||||
let lastPageIndex = numberOfPages + 1
|
||||
let goToIndex = { (index: Int) in
|
||||
self.goTo(index, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
self.pagingView?.currentIndex = self.pageIndex
|
||||
}
|
||||
|
||||
if currentIndex < 2 {
|
||||
// If on a "buffer" last row (which is the first index), go to the real last row secretly. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking.
|
||||
goToIndex(lastPageIndex)
|
||||
} else if currentIndex > lastPageIndex {
|
||||
// If on the "buffer" first row (which is the index after the real last row), go to the real first row secretly.
|
||||
goToIndex(2)
|
||||
}
|
||||
}
|
||||
|
||||
func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) {
|
||||
|
||||
guard loop, dragging else { return }
|
||||
|
||||
// Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index.
|
||||
if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing {
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
let index = scrollView.contentOffset.x / (itemWidth + separatorWidth)
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
|
||||
if index < 1 {
|
||||
currentIndex = 0
|
||||
updateModelIndex()
|
||||
} else if index > CGFloat(lastCellIndex - 1) {
|
||||
currentIndex = lastCellIndex
|
||||
updateModelIndex()
|
||||
/// Adjusts the current contentOffset if we are going onto buffer cells while looping to help with the endless scrolling appearance.
|
||||
func adjustOffsetForLooping(_ scrollView: UIScrollView) {
|
||||
let translatedPoint = scrollView.panGestureRecognizer.translation(in: scrollView.superview).x
|
||||
if translatedPoint > 0 {
|
||||
// Moving left, see if we are moving passed the first left buffer card and adjust
|
||||
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0))?.frame.minX,
|
||||
scrollView.contentOffset.x < threshold,
|
||||
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
||||
scrollView.contentOffset.x = newOffset
|
||||
}
|
||||
} else if translatedPoint < 0 {
|
||||
// Moving right, see if we are moving passed the first right buffer card and adjust
|
||||
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 2, section: 0))?.frame.maxX,
|
||||
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
||||
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
||||
scrollView.contentOffset.x = newEndOffset - scrollView.bounds.width
|
||||
}
|
||||
}
|
||||
|
||||
handleUserOnBufferCell()
|
||||
}
|
||||
|
||||
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
// Check if the user is dragging the card even further past the next card.
|
||||
//checkForDraggingOutOfBounds(scrollView)
|
||||
|
||||
// Adjust for looping
|
||||
if let model = model as? CarouselModel,
|
||||
model.loop == true {
|
||||
adjustOffsetForLooping(scrollView)
|
||||
}
|
||||
|
||||
// Let the pager know our progress if needed.
|
||||
pagingView?.scrollViewDidScroll(collectionView)
|
||||
@ -391,6 +387,7 @@ extension Carousel: UIScrollViewDelegate {
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
|
||||
// Disable peaking when dragging.
|
||||
dragging = true
|
||||
showPeaking(false)
|
||||
}
|
||||
@ -398,32 +395,63 @@ extension Carousel: UIScrollViewDelegate {
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
|
||||
dragging = false
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
|
||||
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size).
|
||||
guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return }
|
||||
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). Math requires that we are using UICollectionViewFlowLayout.
|
||||
guard (model as? CarouselModel)?.paging == true,
|
||||
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
|
||||
|
||||
// We switch cards if we pass the velocity threshold or position threshold (currently 50%).
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
var cellToSwipeTo = Int(scrollView.contentOffset.x / (itemWidth + separatorWidth) + 0.5)
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
let velocityThreshold: CGFloat = 1.1
|
||||
let separatorWidth = layout.minimumLineSpacing
|
||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||
let width = itemWidth + separatorWidth
|
||||
|
||||
if velocity.x > velocityThreshold {
|
||||
cellToSwipeTo = currentIndex + 1
|
||||
|
||||
} else if velocity.x < -velocityThreshold {
|
||||
cellToSwipeTo = currentIndex - 1
|
||||
// Adjusts the offset for the contentInset. Adds imaginary half separator to the left of the first card, which is necessary for determining the percent of a given card we are currently at.
|
||||
let adjustedOffset = scrollView.contentOffset.x - layout.sectionInset.left + (separatorWidth / 2)
|
||||
|
||||
// Calculates the offset per card depending on the alignment.
|
||||
var offsetByCard: CGFloat
|
||||
switch itemAlignment {
|
||||
case .right:
|
||||
offsetByCard = ((adjustedOffset + scrollView.bounds.width) / width) - 1
|
||||
case .centeredHorizontally:
|
||||
offsetByCard = ((adjustedOffset + (scrollView.bounds.width / 2)) / width) - 0.5
|
||||
default:
|
||||
offsetByCard = adjustedOffset / width
|
||||
}
|
||||
|
||||
// Adjust card for velocity impact.
|
||||
let velocityThreshold: CGFloat = 1.1
|
||||
var cellToSwipeTo: Int
|
||||
if velocity.x > velocityThreshold {
|
||||
cellToSwipeTo = Int(ceil(offsetByCard))
|
||||
} else if velocity.x < -velocityThreshold {
|
||||
cellToSwipeTo = Int(floor(offsetByCard))
|
||||
} else {
|
||||
cellToSwipeTo = Int(round(offsetByCard))
|
||||
}
|
||||
|
||||
// If we are swiping to a buffer cell, change to real cell before beginning animation so we don't go out of bounds.
|
||||
if cellToSwipeTo < 2 {
|
||||
let newOffset = scrollView.contentOffset.x + (width * CGFloat(numberOfPages))
|
||||
scrollView.contentOffset.x = newOffset
|
||||
targetContentOffset.pointee.x = newOffset
|
||||
cellToSwipeTo = cellToSwipeTo + numberOfPages
|
||||
} else if cellToSwipeTo > numberOfPages + 1 {
|
||||
let newOffset = scrollView.contentOffset.x - (width * CGFloat(numberOfPages))
|
||||
scrollView.contentOffset.x = newOffset
|
||||
targetContentOffset.pointee.x = newOffset
|
||||
cellToSwipeTo = cellToSwipeTo - numberOfPages
|
||||
} else {
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
}
|
||||
|
||||
// Cap the index.
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true)
|
||||
}
|
||||
|
||||
// To give the illusion of endless scrolling. Since we are always calling scrollToItem we can assume finished paging in here.
|
||||
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||
// Cycle to other end if on buffer cell.
|
||||
handleUserOnBufferCell()
|
||||
pagingView?.currentIndex = pageIndex
|
||||
showPeaking(true)
|
||||
}
|
||||
|
||||
@ -21,14 +21,18 @@ import UIKit
|
||||
public var backgroundColor: Color?
|
||||
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]
|
||||
public var index: Int = 0
|
||||
public var spacing: Float?
|
||||
public var spacing: CGFloat?
|
||||
public var border: Bool?
|
||||
public var loop: Bool?
|
||||
public var height: Float?
|
||||
public var itemWidthPercent: Float?
|
||||
public var height: CGFloat?
|
||||
@Percent public var itemWidthPercent = 100
|
||||
public var itemAlignment: UICollectionView.ScrollPosition?
|
||||
public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?
|
||||
|
||||
public var paging: Bool = true
|
||||
public var useHorizontalMargins: Bool?
|
||||
public var leftPadding: CGFloat?
|
||||
public var rightPadding: CGFloat?
|
||||
|
||||
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
|
||||
self.molecules = molecules
|
||||
}
|
||||
@ -49,6 +53,10 @@ import UIKit
|
||||
case itemWidthPercent
|
||||
case itemAlignment
|
||||
case pagingMolecule
|
||||
case paging
|
||||
case useHorizontalMargins
|
||||
case leftPadding
|
||||
case rightPadding
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -60,13 +68,21 @@ import UIKit
|
||||
molecules = try typeContainer.decodeModels(codingKey: .molecules)
|
||||
index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0
|
||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||
spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing)
|
||||
spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing)
|
||||
border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border)
|
||||
loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
|
||||
height = try typeContainer.decodeIfPresent(Float.self, forKey: .height)
|
||||
itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent)
|
||||
height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height)
|
||||
if let itemWidthPercent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .itemWidthPercent) {
|
||||
self.itemWidthPercent = itemWidthPercent
|
||||
}
|
||||
itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment)
|
||||
pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule)
|
||||
if let paging = try typeContainer.decodeIfPresent(Bool.self, forKey: .paging) {
|
||||
self.paging = paging
|
||||
}
|
||||
useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins)
|
||||
leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding)
|
||||
rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@ -74,12 +90,16 @@ import UIKit
|
||||
try container.encode(moleculeName, forKey: .moleculeName)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encodeModels(molecules, forKey: .molecules)
|
||||
try container.encode(spacing, forKey: .spacing)
|
||||
try container.encode(border, forKey: .border)
|
||||
try container.encode(loop, forKey: .loop)
|
||||
try container.encode(height, forKey: .height)
|
||||
try container.encodeIfPresent(spacing, forKey: .spacing)
|
||||
try container.encodeIfPresent(border, forKey: .border)
|
||||
try container.encodeIfPresent(loop, forKey: .loop)
|
||||
try container.encodeIfPresent(height, forKey: .height)
|
||||
try container.encode(itemWidthPercent, forKey: .itemWidthPercent)
|
||||
try container.encode(itemAlignment, forKey: .itemAlignment)
|
||||
try container.encodeIfPresent(itemAlignment, forKey: .itemAlignment)
|
||||
try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule)
|
||||
try container.encode(paging, forKey: .paging)
|
||||
try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins)
|
||||
try container.encodeIfPresent(leftPadding, forKey: .leftPadding)
|
||||
try container.encodeIfPresent(rightPadding, forKey: .rightPadding)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,5 +10,6 @@ import Foundation
|
||||
|
||||
|
||||
public protocol CarouselPagingModelProtocol {
|
||||
var position: Float? { get }
|
||||
var position: CGFloat? { get }
|
||||
var hidesForSinglePage: Bool { get }
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
//
|
||||
// TabPageModelProtocol.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 6/1/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol TabPageModelProtocol {
|
||||
var tabBarHidden: Bool { get set }
|
||||
var tabBarIndex: Int? { get set }
|
||||
}
|
||||
17
MVMCoreUI/Atomic/Protocols/TabBarProtocol.swift
Normal file
17
MVMCoreUI/Atomic/Protocols/TabBarProtocol.swift
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// TabBarProtocol.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 5/29/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc public protocol TabBarProtocol {
|
||||
/// Should visually select the given tab index.
|
||||
@objc func highlightTab(at index: Int)
|
||||
|
||||
/// Should select the tab index. As if the user selected it.
|
||||
@objc func selectTab(at index: Int)
|
||||
}
|
||||
@ -131,7 +131,7 @@ import Foundation
|
||||
return cell
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
public override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,9 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol {
|
||||
|
||||
let stack = MoleculeStackView(frame: .zero)
|
||||
moleculeStackModel.useStackSpacingBeforeFirstItem = true
|
||||
moleculeStackModel.useHorizontalMargins = true
|
||||
if moleculeStackModel.useHorizontalMargins == nil {
|
||||
moleculeStackModel.useHorizontalMargins = true
|
||||
}
|
||||
stack.set(with: moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil)
|
||||
return stack
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
@objcMembers public class TemplateModel: MVMControllerModelProtocol {
|
||||
@objcMembers public class TemplateModel: MVMControllerModelProtocol, TabPageModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -29,6 +29,9 @@ import Foundation
|
||||
public var navigationBar: (NavigationItemModelProtocol & MoleculeModelProtocol)?
|
||||
public var formRules: [FormGroupRule]?
|
||||
public var behaviors: [PageBehaviorProtocol]?
|
||||
|
||||
public var tabBarHidden: Bool = false
|
||||
public var tabBarIndex: Int?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
@ -50,6 +53,8 @@ import Foundation
|
||||
case formRules
|
||||
case behaviors
|
||||
case navigationBar
|
||||
case tabBarHidden
|
||||
case tabBarIndex
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -64,6 +69,10 @@ import Foundation
|
||||
formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules)
|
||||
behaviors = try typeContainer.decodeModelsIfPresent(codingKey: .behaviors)
|
||||
navigationBar = try typeContainer.decodeModelIfPresent(codingKey: .navigationBar)
|
||||
if let tabBarHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .tabBarHidden) {
|
||||
self.tabBarHidden = tabBarHidden
|
||||
}
|
||||
tabBarIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .tabBarIndex)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@ -74,5 +83,7 @@ import Foundation
|
||||
try container.encodeIfPresent(screenHeading, forKey: .screenHeading)
|
||||
try container.encodeIfPresent(formRules, forKey: .formRules)
|
||||
try container.encodeModelIfPresent(navigationBar, forKey: .navigationBar)
|
||||
try container.encode(tabBarHidden, forKey: .tabBarHidden)
|
||||
try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,11 +31,7 @@ public typealias BarButtonAction = (BarButtonItem) -> ()
|
||||
open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
buttonDelegate = delegateObject?.buttonDelegate
|
||||
actionDelegate?.buttonAction = { sender in
|
||||
if let data = try? actionModel.encode(using: JSONEncoder()),
|
||||
let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any],
|
||||
delegateObject?.buttonDelegate?.button?(sender, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true {
|
||||
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
|
||||
}
|
||||
Button.performButtonAction(with: actionModel, button: sender, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,11 +78,15 @@ public typealias ButtonAction = (Button) -> ()
|
||||
|
||||
addActionBlock(event: .touchUpInside) { [weak self] sender in
|
||||
guard let self = self else { return }
|
||||
if let data = try? actionModel.encode(using: JSONEncoder()),
|
||||
let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any],
|
||||
delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true {
|
||||
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
|
||||
}
|
||||
Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
if let data = try? model.encode(using: JSONEncoder()),
|
||||
let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any],
|
||||
delegateObject?.buttonDelegate?.button?(button, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true {
|
||||
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
|
||||
/// A base collection view cell with basic mvm functionality.
|
||||
open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol, CollectionTemplateItemProtocol {
|
||||
open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol, CollectionTemplateItemProtocol, MFButtonProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -25,10 +25,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
||||
|
||||
private var initialSetupPerformed = false
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
// MARK: - Inits
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
@ -47,10 +43,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
open func setupView() {
|
||||
isAccessibilityElement = false
|
||||
@ -68,16 +60,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
||||
(molecule as? MVMCoreViewProtocol)?.updateView(size)
|
||||
}
|
||||
|
||||
open func reset() {
|
||||
molecule?.reset()
|
||||
backgroundColor = .mvmWhite
|
||||
width = nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
// MARK: - MoleculeViewProtocol
|
||||
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
guard let model = model as? CollectionItemModelProtocol else { return }
|
||||
@ -94,6 +76,12 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
||||
}
|
||||
}
|
||||
|
||||
open func reset() {
|
||||
molecule?.reset()
|
||||
backgroundColor = .mvmWhite
|
||||
width = nil
|
||||
}
|
||||
|
||||
/// Convenience function. Adds a molecule to the view.
|
||||
open func addMolecule(_ molecule: MoleculeViewProtocol) {
|
||||
contentView.addSubview(molecule)
|
||||
@ -109,6 +97,12 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
||||
self.width = width
|
||||
}
|
||||
|
||||
// MARK: - Override
|
||||
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
guard let action = model?.action else { return }
|
||||
Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
// Column logic, set width.
|
||||
override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
|
||||
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
|
||||
|
||||
@ -9,5 +9,13 @@
|
||||
import Foundation
|
||||
|
||||
public protocol CollectionItemModelProtocol {
|
||||
|
||||
var action: ActionModelProtocol? { get set }
|
||||
}
|
||||
|
||||
// Not a strict requirement.
|
||||
public extension CollectionItemModelProtocol {
|
||||
var action: ActionModelProtocol? {
|
||||
get { return nil }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
@objcMembers open class TableViewCell: UITableViewCell, MoleculeViewProtocol, MoleculeListCellProtocol, MVMCoreViewProtocol {
|
||||
@objcMembers open class TableViewCell: UITableViewCell, MoleculeViewProtocol, MoleculeListCellProtocol, MVMCoreViewProtocol, MFButtonProtocol {
|
||||
|
||||
open var molecule: MoleculeViewProtocol?
|
||||
open var listItemModel: ListItemModelProtocol?
|
||||
@ -267,10 +267,8 @@ import UIKit
|
||||
}
|
||||
|
||||
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
//TODO: Use object when handleAction is rewrote to handle action model
|
||||
if let actionMap = self.listItemModel?.action?.toJSON() {
|
||||
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
|
||||
}
|
||||
guard let action = listItemModel?.action else { return }
|
||||
Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
public func willDisplay() {
|
||||
|
||||
@ -233,4 +233,8 @@ import Foundation
|
||||
}
|
||||
fatalError()
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate {
|
||||
@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate {
|
||||
@objc public var pageType: String?
|
||||
@objc public var loadObject: MVMCoreLoadObject?
|
||||
public var pageModel: MVMControllerModelProtocol?
|
||||
@ -259,6 +259,16 @@ import UIKit
|
||||
MVMCoreUISplitViewController.main()?.setBottomProgressBarProgress(progress / Float(100))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TabBar
|
||||
open func updateTabBar() {
|
||||
guard MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() == self,
|
||||
var tabModel = pageModel as? TabPageModelProtocol else { return }
|
||||
if let index = tabModel.tabBarIndex {
|
||||
MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: index)
|
||||
}
|
||||
MVMCoreUISplitViewController.main()?.updateTabBarShowing(!tabModel.tabBarHidden)
|
||||
}
|
||||
|
||||
// MARK: - View lifecycle
|
||||
|
||||
@ -323,6 +333,9 @@ import UIKit
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// Update tab if needed.
|
||||
updateTabBar()
|
||||
|
||||
if manager == nil {
|
||||
MVMCoreUISession.sharedGlobal()?.currentPageType = pageType
|
||||
MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
|
||||
@ -371,6 +384,28 @@ import UIKit
|
||||
MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreLoadDelegateProtocol
|
||||
// TODO: Move this function out of here after architecture cleanup.
|
||||
open func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) {
|
||||
|
||||
MVMCoreUILoggingHandler.log(withDelegateLoadFinished: loadObject, loadedViewController: loadedViewController, error: error)
|
||||
|
||||
// Open the support panel
|
||||
if error == nil,
|
||||
loadObject?.requestParameters?.openSupportPanel ?? (loadObject?.systemParametersJSON?.boolForKey(KeyOpenSupport) ?? false) == true {
|
||||
MVMCoreUISession.sharedGlobal()?.splitViewController?.showRightPanel(animated: true)
|
||||
}
|
||||
|
||||
// Selects the tab if needed. Page driven takes priority over action driven (see viewWillAppear)
|
||||
if let tab: Int = loadObject?.requestParameters?.actionMap?["tabBarIndex"] as? Int,
|
||||
error == nil,
|
||||
loadObject?.pageJSON?["tabBarIndex"] == nil {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: tab)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreActionDelegateProtocol
|
||||
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
||||
formValidator?.addFormParams(requestParameters: requestParameters)
|
||||
|
||||
@ -11,9 +11,11 @@
|
||||
@import MVMCore.MVMCoreActionDelegateProtocol;
|
||||
#import <MVMCoreUI/MVMCoreUIPanelProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreUIPanelButtonProtocol.h>
|
||||
|
||||
@class MVMCoreUITopAlertView;
|
||||
@class MFViewController;
|
||||
@class NavigationController;
|
||||
@protocol TabBarProtocol;
|
||||
|
||||
typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
MFNoDrawer = 0,
|
||||
@ -47,6 +49,9 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
@property (nonatomic, readonly) BOOL rightPanelIsAccessible;
|
||||
@property (nullable, weak, nonatomic, readonly) UIViewController *navigationItemViewController;
|
||||
|
||||
/// Reference to the tabbar.
|
||||
@property (nullable, weak, nonatomic) UIView <TabBarProtocol>*tabBar;
|
||||
|
||||
// Convenience getter
|
||||
+ (nullable instancetype)mainSplitViewController;
|
||||
|
||||
@ -141,4 +146,15 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||
- (IBAction)backButtonPressed:(nullable id)sender;
|
||||
- (IBAction)rightPanelButtonPressed:(nullable id)sender;
|
||||
|
||||
#pragma mark - TabBar
|
||||
|
||||
/// Called when split view is loaded to create the initial tabbar. Default is nil.
|
||||
- (nullable UIView <TabBarProtocol>*)createTabBar;
|
||||
|
||||
/// Adds any tabbar at the bottom of the split view.
|
||||
- (void)addTabBar:(nonnull UIView <TabBarProtocol>*)tabBar;
|
||||
|
||||
/// Updates if the tab bar is showing or not.
|
||||
- (void)updateTabBarShowing:(BOOL)showing;
|
||||
|
||||
@end
|
||||
|
||||
@ -43,6 +43,8 @@ typedef NS_OPTIONS(NSInteger, MFExtendedDrawer) {
|
||||
@property (weak, nonatomic) UIView *leftPanelSeparator;
|
||||
@property (weak, nonatomic) UIView *rightPanelSeparator;
|
||||
|
||||
@property (weak, nonatomic) NSLayoutConstraint *bottomConstraint;
|
||||
|
||||
@property (weak, nonatomic, readwrite) NavigationController *navigationController;
|
||||
|
||||
// A view that covers the detail view when the master is out.
|
||||
@ -716,7 +718,7 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
self.leftPanelWidth = leftPanelWidth;
|
||||
[NSLayoutConstraint constraintWithItem:self.mainView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.leftView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.leftView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.leftView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.leftView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||
|
||||
if ([panel respondsToSelector:@selector(buttonForPanel)]) {
|
||||
self.leftPanelButton = [panel buttonForPanel];
|
||||
@ -751,7 +753,7 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
self.rightPanelWidth = rightPanelWidth;
|
||||
[NSLayoutConstraint constraintWithItem:self.rightView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.rightView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.rightView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.rightView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||
|
||||
if ([panel respondsToSelector:@selector(buttonForPanel)]) {
|
||||
self.rightPanelButton = [panel buttonForPanel];
|
||||
@ -771,6 +773,36 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - TabBar
|
||||
|
||||
- (nullable UIView <TabBarProtocol>*)createTabBar {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)addTabBar:(nonnull UIView <TabBarProtocol>*)tabBar {
|
||||
[self.view insertSubview:tabBar atIndex:0];
|
||||
self.tabBar = tabBar;
|
||||
[tabBar.topAnchor constraintEqualToAnchor:self.bottomProgressBar.bottomAnchor].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:tabBar attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:tabBar attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.mainView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0].active = YES;
|
||||
[self updateTabBarShowing:YES];
|
||||
}
|
||||
|
||||
- (void)updateTabBarShowing:(BOOL)showing {
|
||||
self.tabBar.hidden = !showing;
|
||||
self.bottomConstraint.active = NO;
|
||||
if (showing && self.tabBar) {
|
||||
NSLayoutConstraint *bottom = [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.tabBar.bottomAnchor];
|
||||
bottom.active = YES;
|
||||
self.bottomConstraint = bottom;
|
||||
} else {
|
||||
NSLayoutConstraint *bottom = [self.view.bottomAnchor constraintEqualToAnchor:self.bottomProgressBar.bottomAnchor];
|
||||
bottom.active = YES;
|
||||
self.bottomConstraint = bottom;
|
||||
}
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - Bottom Progress Bar
|
||||
|
||||
- (void)setBottomProgressBarProgress:(float)progress {
|
||||
@ -841,9 +873,17 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
self.bottomProgressBarHeightConstraint = bottomProgressHeight;
|
||||
|
||||
if (topAlertView) {
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[topAlertView]-0-[mainView]-0-[progressView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topAlertView, mainView, progressView)]];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[topAlertView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topAlertView, mainView, progressView)]];
|
||||
} else {
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[mainView]-0-[progressView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(mainView, progressView)]];
|
||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(mainView, progressView)]];
|
||||
}
|
||||
|
||||
// Add tabbar if we have it.
|
||||
UIView <TabBarProtocol>*tabs = [self createTabBar];
|
||||
if (tabs) {
|
||||
[self addTabBar:tabs];
|
||||
} else {
|
||||
[self updateTabBarShowing:NO];
|
||||
}
|
||||
|
||||
// Cover View
|
||||
@ -855,8 +895,8 @@ CGFloat const PanelAnimationDuration = 0.2;
|
||||
self.mainViewCoverView = coverView;
|
||||
[NSLayoutConstraint constraintWithItem:coverView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:mainView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:coverView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:mainView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:coverView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:mainView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:coverView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:mainView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:coverView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0].active = YES;
|
||||
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:coverView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||
|
||||
[self setupPanels];
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user