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 */; };
|
D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */; };
|
||||||
D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */; };
|
D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */; };
|
||||||
D28BA730247EC2EB00B75CB8 /* NavigationButtomModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA72F247EC2EB00B75CB8 /* NavigationButtomModelProtocol.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, ); }; };
|
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 */; };
|
D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */; };
|
||||||
D29C94D5242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUICommonViewsUtility+Extension.swift"; sourceTree = "<group>"; };
|
||||||
@ -910,6 +918,7 @@
|
|||||||
D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */,
|
D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */,
|
||||||
D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */,
|
D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */,
|
||||||
D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */,
|
D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */,
|
||||||
|
D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */,
|
||||||
);
|
);
|
||||||
path = ModelProtocols;
|
path = ModelProtocols;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1235,6 +1244,8 @@
|
|||||||
017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */,
|
017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */,
|
||||||
D28764FA245A33A500CB882D /* TwoLinkViewModel.swift */,
|
D28764FA245A33A500CB882D /* TwoLinkViewModel.swift */,
|
||||||
D28764F8245A327200CB882D /* TwoLinkView.swift */,
|
D28764F8245A327200CB882D /* TwoLinkView.swift */,
|
||||||
|
D28BA740248025A300B75CB8 /* TabBarModel.swift */,
|
||||||
|
D28BA7422480284E00B75CB8 /* TabBar.swift */,
|
||||||
);
|
);
|
||||||
path = HorizontalCombinationViews;
|
path = HorizontalCombinationViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1892,6 +1903,7 @@
|
|||||||
012A88C7238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift */,
|
012A88C7238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift */,
|
||||||
017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */,
|
017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */,
|
||||||
012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */,
|
012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */,
|
||||||
|
D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */,
|
||||||
011B58EE23A2AA850085F53C /* ModelProtocols */,
|
011B58EE23A2AA850085F53C /* ModelProtocols */,
|
||||||
);
|
);
|
||||||
path = Protocols;
|
path = Protocols;
|
||||||
@ -2042,6 +2054,7 @@
|
|||||||
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
|
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
|
||||||
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
|
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
|
||||||
D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */,
|
D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */,
|
||||||
|
D28BA7452481652D00B75CB8 /* TabBarProtocol.swift in Sources */,
|
||||||
AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */,
|
AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */,
|
||||||
D28764AA2458980300CB882D /* ThreeLayerFillMiddleTemplate.swift in Sources */,
|
D28764AA2458980300CB882D /* ThreeLayerFillMiddleTemplate.swift in Sources */,
|
||||||
0116A4E5228B19640094F3ED /* RadioButtonSelectionHelper.swift in Sources */,
|
0116A4E5228B19640094F3ED /* RadioButtonSelectionHelper.swift in Sources */,
|
||||||
@ -2109,6 +2122,7 @@
|
|||||||
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */,
|
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */,
|
||||||
D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */,
|
D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */,
|
||||||
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */,
|
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */,
|
||||||
|
D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */,
|
||||||
014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */,
|
014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */,
|
||||||
0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */,
|
0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */,
|
||||||
BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */,
|
BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */,
|
||||||
@ -2287,6 +2301,7 @@
|
|||||||
D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */,
|
D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */,
|
||||||
AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */,
|
AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */,
|
||||||
DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */,
|
DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */,
|
||||||
|
D28BA741248025A300B75CB8 /* TabBarModel.swift in Sources */,
|
||||||
D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */,
|
D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */,
|
||||||
D2A92882241AAB67004E01C6 /* ScrollingViewController.swift in Sources */,
|
D2A92882241AAB67004E01C6 /* ScrollingViewController.swift in Sources */,
|
||||||
C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */,
|
C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */,
|
||||||
@ -2387,6 +2402,7 @@
|
|||||||
0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */,
|
0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */,
|
||||||
D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */,
|
D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */,
|
||||||
94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */,
|
94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */,
|
||||||
|
D28BA7432480284E00B75CB8 /* TabBar.swift in Sources */,
|
||||||
AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */,
|
AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */,
|
||||||
011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */,
|
011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */,
|
||||||
D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */,
|
D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */,
|
||||||
|
|||||||
@ -206,6 +206,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
isEnabled = model.enabled
|
isEnabled = model.enabled
|
||||||
|
|
||||||
formatAccessibilityValue(index: currentIndex + 1, total: numberOfPages)
|
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 disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3)
|
||||||
public var indicatorColor: Color = Color(uiColor: .mvmBlack)
|
public var indicatorColor: Color = Color(uiColor: .mvmBlack)
|
||||||
public var indicatorColor_inverted: Color = Color(uiColor: .mvmWhite)
|
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.
|
/// Allows sendActions() to trigger even if index is already at min/max index.
|
||||||
public var alwaysSendAction = false
|
public var alwaysSendAction = false
|
||||||
@ -79,7 +79,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
|||||||
self.inverted = inverted
|
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
|
self.position = position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,15 +14,17 @@ import Foundation
|
|||||||
public let leftImage = LoadImageView()
|
public let leftImage = LoadImageView()
|
||||||
public let headlineBody = HeadlineBody()
|
public let headlineBody = HeadlineBody()
|
||||||
public let rightLabel = Label.createLabelRegularBodySmall(true)
|
public let rightLabel = Label.createLabelRegularBodySmall(true)
|
||||||
|
public let rightLabelStackItem: StackItem
|
||||||
public var stack: Stack<StackModel>
|
public var stack: Stack<StackModel>
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
stack = Stack<StackModel>.createStack(with: [(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)),
|
rightLabelStackItem = StackItem(andContain: rightLabel)
|
||||||
(view: headlineBody, model: StackItemModel(horizontalAlignment: .leading)),
|
let stackItems = [StackItem(andContain: leftImage), StackItem(andContain: headlineBody), rightLabelStackItem]
|
||||||
(view: rightLabel, model: StackItemModel(horizontalAlignment: .fill, verticalAlignment: .leading))], axis: .horizontal)
|
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)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +32,16 @@ import Foundation
|
|||||||
fatalError("init(coder:) has not been implemented")
|
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
|
// 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.encodeModel(label, forKey: .label)
|
||||||
try container.encodeModelIfPresent(action, forKey: .action)
|
try container.encodeModelIfPresent(action, forKey: .action)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,12 @@ import Foundation
|
|||||||
open override class var identifier: String {
|
open override class var identifier: String {
|
||||||
return "collectionItem"
|
return "collectionItem"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var action: ActionModelProtocol?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case action
|
||||||
|
}
|
||||||
|
|
||||||
/// Defaults to set
|
/// Defaults to set
|
||||||
public override func setDefaults() {
|
public override func setDefaults() {
|
||||||
@ -35,10 +41,14 @@ import Foundation
|
|||||||
}
|
}
|
||||||
|
|
||||||
required public init(from decoder: Decoder) throws {
|
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)
|
try super.init(from: decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func encode(to encoder: Encoder) throws {
|
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)
|
try super.encode(to: encoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,14 @@ public protocol CarouselPageControlProtocol {
|
|||||||
|
|
||||||
open class Carousel: View {
|
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.
|
/// The current index of the collection view. Includes dummy cells when looping.
|
||||||
public var currentIndex = 0
|
public var currentIndex = 0
|
||||||
|
|
||||||
@ -36,13 +42,13 @@ open class Carousel: View {
|
|||||||
open var numberOfPages = 0
|
open var numberOfPages = 0
|
||||||
|
|
||||||
/// The models for the molecules.
|
/// 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%.
|
/// 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
|
public var itemAlignment = UICollectionView.ScrollPosition.left
|
||||||
|
|
||||||
/// From 0-1. The item width as a percent of the carousel width.
|
/// 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.
|
/// The height of the carousel. Default is 300.
|
||||||
public var collectionViewHeight: NSLayoutConstraint?
|
public var collectionViewHeight: NSLayoutConstraint?
|
||||||
@ -51,7 +57,7 @@ open class Carousel: View {
|
|||||||
public var pagingView: (UIView & CarouselPageControlProtocol)?
|
public var pagingView: (UIView & CarouselPageControlProtocol)?
|
||||||
|
|
||||||
/// If the carousel should loop after scrolling past the first and final cells.
|
/// If the carousel should loop after scrolling past the first and final cells.
|
||||||
var loop = false
|
public var loop = false
|
||||||
|
|
||||||
private var dragging = false
|
private var dragging = false
|
||||||
|
|
||||||
@ -81,6 +87,8 @@ open class Carousel: View {
|
|||||||
showPeaking(false)
|
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.
|
// 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 {
|
DispatchQueue.main.async {
|
||||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||||
self.collectionView.layoutIfNeeded()
|
self.collectionView.layoutIfNeeded()
|
||||||
@ -98,15 +106,23 @@ open class Carousel: View {
|
|||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
addSubview(collectionView)
|
addSubview(collectionView)
|
||||||
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
|
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
|
||||||
|
|
||||||
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
||||||
collectionViewHeight?.isActive = false
|
collectionViewHeight?.isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func updateView(_ size: CGFloat) {
|
open override func updateView(_ size: CGFloat) {
|
||||||
super.updateView(size)
|
super.updateView(size)
|
||||||
self.size = 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.
|
// Update cells and re-layout.
|
||||||
for cell in collectionView.visibleCells {
|
for cell in collectionView.visibleCells {
|
||||||
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
||||||
@ -128,19 +144,19 @@ open class Carousel: View {
|
|||||||
collectionView.layer.borderColor = backgroundColor?.cgColor
|
collectionView.layer.borderColor = backgroundColor?.cgColor
|
||||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||||
backgroundColor = .white
|
backgroundColor = .white
|
||||||
|
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0
|
||||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
|
||||||
setupLayout(with: carouselModel)
|
itemWidthPercent = carouselModel.itemWidthPercent / 100.0
|
||||||
prepareMolecules(with: carouselModel)
|
|
||||||
itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100
|
|
||||||
if let alignment = carouselModel.itemAlignment {
|
if let alignment = carouselModel.itemAlignment {
|
||||||
itemAlignment = alignment
|
itemAlignment = alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
if let height = carouselModel.height {
|
if let height = carouselModel.height {
|
||||||
collectionViewHeight?.constant = CGFloat(height)
|
collectionViewHeight?.constant = height
|
||||||
collectionViewHeight?.isActive = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||||
|
prepareMolecules(with: carouselModel)
|
||||||
|
|
||||||
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
||||||
|
|
||||||
@ -153,16 +169,6 @@ open class Carousel: View {
|
|||||||
// MARK: - JSON Setters
|
// 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?) {
|
func prepareMolecules(with carouselModel: CarouselModel?) {
|
||||||
guard let newMolecules = carouselModel?.molecules else {
|
guard let newMolecules = carouselModel?.molecules else {
|
||||||
numberOfPages = 0
|
numberOfPages = 0
|
||||||
@ -187,11 +193,12 @@ open class Carousel: View {
|
|||||||
/// Sets up the paging molecule
|
/// Sets up the paging molecule
|
||||||
open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) {
|
open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
var pagingView: (UIView & CarouselPageControlProtocol)? = nil
|
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)
|
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
|
/// Registers the cells with the collection view
|
||||||
@ -294,7 +301,7 @@ open class Carousel: View {
|
|||||||
|
|
||||||
extension Carousel: UICollectionViewDelegateFlowLayout {
|
extension Carousel: UICollectionViewDelegateFlowLayout {
|
||||||
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
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)
|
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 {
|
extension Carousel: UIScrollViewDelegate {
|
||||||
|
|
||||||
|
/// Go to the cell at the specified index.
|
||||||
func goTo(_ index: Int, animated: Bool) {
|
func goTo(_ index: Int, animated: Bool) {
|
||||||
|
|
||||||
showPeaking(false)
|
showPeaking(false)
|
||||||
@ -339,51 +353,33 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUserOnBufferCell() {
|
/// Adjusts the current contentOffset if we are going onto buffer cells while looping to help with the endless scrolling appearance.
|
||||||
guard loop else { return }
|
func adjustOffsetForLooping(_ scrollView: UIScrollView) {
|
||||||
|
let translatedPoint = scrollView.panGestureRecognizer.translation(in: scrollView.superview).x
|
||||||
let lastPageIndex = numberOfPages + 1
|
if translatedPoint > 0 {
|
||||||
let goToIndex = { (index: Int) in
|
// Moving left, see if we are moving passed the first left buffer card and adjust
|
||||||
self.goTo(index, animated: false)
|
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0))?.frame.minX,
|
||||||
self.collectionView.layoutIfNeeded()
|
scrollView.contentOffset.x < threshold,
|
||||||
self.pagingView?.currentIndex = self.pageIndex
|
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
||||||
}
|
scrollView.contentOffset.x = newOffset
|
||||||
|
}
|
||||||
if currentIndex < 2 {
|
} else if translatedPoint < 0 {
|
||||||
// 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.
|
// Moving right, see if we are moving passed the first right buffer card and adjust
|
||||||
goToIndex(lastPageIndex)
|
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 2, section: 0))?.frame.maxX,
|
||||||
} else if currentIndex > lastPageIndex {
|
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
||||||
// If on the "buffer" first row (which is the index after the real last row), go to the real first row secretly.
|
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
||||||
goToIndex(2)
|
scrollView.contentOffset.x = newEndOffset - scrollView.bounds.width
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUserOnBufferCell()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
|
||||||
// Check if the user is dragging the card even further past the next card.
|
// Adjust for looping
|
||||||
//checkForDraggingOutOfBounds(scrollView)
|
if let model = model as? CarouselModel,
|
||||||
|
model.loop == true {
|
||||||
|
adjustOffsetForLooping(scrollView)
|
||||||
|
}
|
||||||
|
|
||||||
// Let the pager know our progress if needed.
|
// Let the pager know our progress if needed.
|
||||||
pagingView?.scrollViewDidScroll(collectionView)
|
pagingView?.scrollViewDidScroll(collectionView)
|
||||||
@ -391,6 +387,7 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
|
|
||||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
|
||||||
|
// Disable peaking when dragging.
|
||||||
dragging = true
|
dragging = true
|
||||||
showPeaking(false)
|
showPeaking(false)
|
||||||
}
|
}
|
||||||
@ -398,32 +395,63 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||||
|
|
||||||
dragging = false
|
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).
|
// 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 let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return }
|
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 separatorWidth = layout.minimumLineSpacing
|
||||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||||
var cellToSwipeTo = Int(scrollView.contentOffset.x / (itemWidth + separatorWidth) + 0.5)
|
let width = itemWidth + separatorWidth
|
||||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
|
||||||
let velocityThreshold: CGFloat = 1.1
|
|
||||||
|
|
||||||
if velocity.x > velocityThreshold {
|
// 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.
|
||||||
cellToSwipeTo = currentIndex + 1
|
let adjustedOffset = scrollView.contentOffset.x - layout.sectionInset.left + (separatorWidth / 2)
|
||||||
|
|
||||||
} else if velocity.x < -velocityThreshold {
|
// Calculates the offset per card depending on the alignment.
|
||||||
cellToSwipeTo = currentIndex - 1
|
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.
|
// Cap the index.
|
||||||
|
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||||
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true)
|
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.
|
// 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) {
|
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||||
// Cycle to other end if on buffer cell.
|
// Cycle to other end if on buffer cell.
|
||||||
handleUserOnBufferCell()
|
|
||||||
pagingView?.currentIndex = pageIndex
|
pagingView?.currentIndex = pageIndex
|
||||||
showPeaking(true)
|
showPeaking(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,14 +21,18 @@ import UIKit
|
|||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]
|
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]
|
||||||
public var index: Int = 0
|
public var index: Int = 0
|
||||||
public var spacing: Float?
|
public var spacing: CGFloat?
|
||||||
public var border: Bool?
|
public var border: Bool?
|
||||||
public var loop: Bool?
|
public var loop: Bool?
|
||||||
public var height: Float?
|
public var height: CGFloat?
|
||||||
public var itemWidthPercent: Float?
|
@Percent public var itemWidthPercent = 100
|
||||||
public var itemAlignment: UICollectionView.ScrollPosition?
|
public var itemAlignment: UICollectionView.ScrollPosition?
|
||||||
public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?
|
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]) {
|
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
|
||||||
self.molecules = molecules
|
self.molecules = molecules
|
||||||
}
|
}
|
||||||
@ -49,6 +53,10 @@ import UIKit
|
|||||||
case itemWidthPercent
|
case itemWidthPercent
|
||||||
case itemAlignment
|
case itemAlignment
|
||||||
case pagingMolecule
|
case pagingMolecule
|
||||||
|
case paging
|
||||||
|
case useHorizontalMargins
|
||||||
|
case leftPadding
|
||||||
|
case rightPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -60,13 +68,21 @@ import UIKit
|
|||||||
molecules = try typeContainer.decodeModels(codingKey: .molecules)
|
molecules = try typeContainer.decodeModels(codingKey: .molecules)
|
||||||
index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0
|
index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
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)
|
border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border)
|
||||||
loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
|
loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
|
||||||
height = try typeContainer.decodeIfPresent(Float.self, forKey: .height)
|
height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height)
|
||||||
itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent)
|
if let itemWidthPercent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .itemWidthPercent) {
|
||||||
|
self.itemWidthPercent = itemWidthPercent
|
||||||
|
}
|
||||||
itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment)
|
itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment)
|
||||||
pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule)
|
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 {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -74,12 +90,16 @@ import UIKit
|
|||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
try container.encodeModels(molecules, forKey: .molecules)
|
try container.encodeModels(molecules, forKey: .molecules)
|
||||||
try container.encode(spacing, forKey: .spacing)
|
try container.encodeIfPresent(spacing, forKey: .spacing)
|
||||||
try container.encode(border, forKey: .border)
|
try container.encodeIfPresent(border, forKey: .border)
|
||||||
try container.encode(loop, forKey: .loop)
|
try container.encodeIfPresent(loop, forKey: .loop)
|
||||||
try container.encode(height, forKey: .height)
|
try container.encodeIfPresent(height, forKey: .height)
|
||||||
try container.encode(itemWidthPercent, forKey: .itemWidthPercent)
|
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.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 {
|
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
|
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)
|
(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)
|
let stack = MoleculeStackView(frame: .zero)
|
||||||
moleculeStackModel.useStackSpacingBeforeFirstItem = true
|
moleculeStackModel.useStackSpacingBeforeFirstItem = true
|
||||||
moleculeStackModel.useHorizontalMargins = true
|
if moleculeStackModel.useHorizontalMargins == nil {
|
||||||
|
moleculeStackModel.useHorizontalMargins = true
|
||||||
|
}
|
||||||
stack.set(with: moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil)
|
stack.set(with: moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil)
|
||||||
return stack
|
return stack
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
@objcMembers public class TemplateModel: MVMControllerModelProtocol {
|
@objcMembers public class TemplateModel: MVMControllerModelProtocol, TabPageModelProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -29,6 +29,9 @@ import Foundation
|
|||||||
public var navigationBar: (NavigationItemModelProtocol & MoleculeModelProtocol)?
|
public var navigationBar: (NavigationItemModelProtocol & MoleculeModelProtocol)?
|
||||||
public var formRules: [FormGroupRule]?
|
public var formRules: [FormGroupRule]?
|
||||||
public var behaviors: [PageBehaviorProtocol]?
|
public var behaviors: [PageBehaviorProtocol]?
|
||||||
|
|
||||||
|
public var tabBarHidden: Bool = false
|
||||||
|
public var tabBarIndex: Int?
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Initializer
|
// MARK: - Initializer
|
||||||
@ -50,6 +53,8 @@ import Foundation
|
|||||||
case formRules
|
case formRules
|
||||||
case behaviors
|
case behaviors
|
||||||
case navigationBar
|
case navigationBar
|
||||||
|
case tabBarHidden
|
||||||
|
case tabBarIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -64,6 +69,10 @@ import Foundation
|
|||||||
formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules)
|
formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules)
|
||||||
behaviors = try typeContainer.decodeModelsIfPresent(codingKey: .behaviors)
|
behaviors = try typeContainer.decodeModelsIfPresent(codingKey: .behaviors)
|
||||||
navigationBar = try typeContainer.decodeModelIfPresent(codingKey: .navigationBar)
|
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 {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -74,5 +83,7 @@ import Foundation
|
|||||||
try container.encodeIfPresent(screenHeading, forKey: .screenHeading)
|
try container.encodeIfPresent(screenHeading, forKey: .screenHeading)
|
||||||
try container.encodeIfPresent(formRules, forKey: .formRules)
|
try container.encodeIfPresent(formRules, forKey: .formRules)
|
||||||
try container.encodeModelIfPresent(navigationBar, forKey: .navigationBar)
|
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]?) {
|
open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||||
buttonDelegate = delegateObject?.buttonDelegate
|
buttonDelegate = delegateObject?.buttonDelegate
|
||||||
actionDelegate?.buttonAction = { sender in
|
actionDelegate?.buttonAction = { sender in
|
||||||
if let data = try? actionModel.encode(using: JSONEncoder()),
|
Button.performButtonAction(with: actionModel, button: sender, delegateObject: delegateObject, additionalData: additionalData)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,11 +78,15 @@ public typealias ButtonAction = (Button) -> ()
|
|||||||
|
|
||||||
addActionBlock(event: .touchUpInside) { [weak self] sender in
|
addActionBlock(event: .touchUpInside) { [weak self] sender in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if let data = try? actionModel.encode(using: JSONEncoder()),
|
Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||||
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)
|
|
||||||
}
|
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.
|
/// 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
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -25,10 +25,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
|
|
||||||
private var initialSetupPerformed = false
|
private var initialSetupPerformed = false
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
// MARK: - Inits
|
// MARK: - Inits
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
@ -47,10 +43,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
// MARK: - MVMCoreViewProtocol
|
// MARK: - MVMCoreViewProtocol
|
||||||
open func setupView() {
|
open func setupView() {
|
||||||
isAccessibilityElement = false
|
isAccessibilityElement = false
|
||||||
@ -68,16 +60,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
(molecule as? MVMCoreViewProtocol)?.updateView(size)
|
(molecule as? MVMCoreViewProtocol)?.updateView(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func reset() {
|
|
||||||
molecule?.reset()
|
|
||||||
backgroundColor = .mvmWhite
|
|
||||||
width = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
// MARK: - MoleculeViewProtocol
|
// MARK: - MoleculeViewProtocol
|
||||||
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
guard let model = model as? CollectionItemModelProtocol else { return }
|
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.
|
/// Convenience function. Adds a molecule to the view.
|
||||||
open func addMolecule(_ molecule: MoleculeViewProtocol) {
|
open func addMolecule(_ molecule: MoleculeViewProtocol) {
|
||||||
contentView.addSubview(molecule)
|
contentView.addSubview(molecule)
|
||||||
@ -109,6 +97,12 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
self.width = width
|
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.
|
// Column logic, set width.
|
||||||
override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
|
override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
|
||||||
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
|
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
|
||||||
|
|||||||
@ -9,5 +9,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol CollectionItemModelProtocol {
|
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
|
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 molecule: MoleculeViewProtocol?
|
||||||
open var listItemModel: ListItemModelProtocol?
|
open var listItemModel: ListItemModelProtocol?
|
||||||
@ -267,10 +267,8 @@ import UIKit
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||||
//TODO: Use object when handleAction is rewrote to handle action model
|
guard let action = listItemModel?.action else { return }
|
||||||
if let actionMap = self.listItemModel?.action?.toJSON() {
|
Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||||
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func willDisplay() {
|
public func willDisplay() {
|
||||||
|
|||||||
@ -233,4 +233,8 @@ import Foundation
|
|||||||
}
|
}
|
||||||
fatalError()
|
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
|
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 pageType: String?
|
||||||
@objc public var loadObject: MVMCoreLoadObject?
|
@objc public var loadObject: MVMCoreLoadObject?
|
||||||
public var pageModel: MVMControllerModelProtocol?
|
public var pageModel: MVMControllerModelProtocol?
|
||||||
@ -259,6 +259,16 @@ import UIKit
|
|||||||
MVMCoreUISplitViewController.main()?.setBottomProgressBarProgress(progress / Float(100))
|
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
|
// MARK: - View lifecycle
|
||||||
|
|
||||||
@ -323,6 +333,9 @@ import UIKit
|
|||||||
open override func viewDidAppear(_ animated: Bool) {
|
open override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
// Update tab if needed.
|
||||||
|
updateTabBar()
|
||||||
|
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
MVMCoreUISession.sharedGlobal()?.currentPageType = pageType
|
MVMCoreUISession.sharedGlobal()?.currentPageType = pageType
|
||||||
MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
|
MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
|
||||||
@ -371,6 +384,28 @@ import UIKit
|
|||||||
MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
|
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
|
// MARK: - MVMCoreActionDelegateProtocol
|
||||||
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
||||||
formValidator?.addFormParams(requestParameters: requestParameters)
|
formValidator?.addFormParams(requestParameters: requestParameters)
|
||||||
|
|||||||
@ -11,9 +11,11 @@
|
|||||||
@import MVMCore.MVMCoreActionDelegateProtocol;
|
@import MVMCore.MVMCoreActionDelegateProtocol;
|
||||||
#import <MVMCoreUI/MVMCoreUIPanelProtocol.h>
|
#import <MVMCoreUI/MVMCoreUIPanelProtocol.h>
|
||||||
#import <MVMCoreUI/MVMCoreUIPanelButtonProtocol.h>
|
#import <MVMCoreUI/MVMCoreUIPanelButtonProtocol.h>
|
||||||
|
|
||||||
@class MVMCoreUITopAlertView;
|
@class MVMCoreUITopAlertView;
|
||||||
@class MFViewController;
|
@class MFViewController;
|
||||||
@class NavigationController;
|
@class NavigationController;
|
||||||
|
@protocol TabBarProtocol;
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
||||||
MFNoDrawer = 0,
|
MFNoDrawer = 0,
|
||||||
@ -47,6 +49,9 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
|||||||
@property (nonatomic, readonly) BOOL rightPanelIsAccessible;
|
@property (nonatomic, readonly) BOOL rightPanelIsAccessible;
|
||||||
@property (nullable, weak, nonatomic, readonly) UIViewController *navigationItemViewController;
|
@property (nullable, weak, nonatomic, readonly) UIViewController *navigationItemViewController;
|
||||||
|
|
||||||
|
/// Reference to the tabbar.
|
||||||
|
@property (nullable, weak, nonatomic) UIView <TabBarProtocol>*tabBar;
|
||||||
|
|
||||||
// Convenience getter
|
// Convenience getter
|
||||||
+ (nullable instancetype)mainSplitViewController;
|
+ (nullable instancetype)mainSplitViewController;
|
||||||
|
|
||||||
@ -141,4 +146,15 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
|
|||||||
- (IBAction)backButtonPressed:(nullable id)sender;
|
- (IBAction)backButtonPressed:(nullable id)sender;
|
||||||
- (IBAction)rightPanelButtonPressed:(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
|
@end
|
||||||
|
|||||||
@ -43,6 +43,8 @@ typedef NS_OPTIONS(NSInteger, MFExtendedDrawer) {
|
|||||||
@property (weak, nonatomic) UIView *leftPanelSeparator;
|
@property (weak, nonatomic) UIView *leftPanelSeparator;
|
||||||
@property (weak, nonatomic) UIView *rightPanelSeparator;
|
@property (weak, nonatomic) UIView *rightPanelSeparator;
|
||||||
|
|
||||||
|
@property (weak, nonatomic) NSLayoutConstraint *bottomConstraint;
|
||||||
|
|
||||||
@property (weak, nonatomic, readwrite) NavigationController *navigationController;
|
@property (weak, nonatomic, readwrite) NavigationController *navigationController;
|
||||||
|
|
||||||
// A view that covers the detail view when the master is out.
|
// A view that covers the detail view when the master is out.
|
||||||
@ -716,7 +718,7 @@ CGFloat const PanelAnimationDuration = 0.2;
|
|||||||
self.leftPanelWidth = leftPanelWidth;
|
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.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: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)]) {
|
if ([panel respondsToSelector:@selector(buttonForPanel)]) {
|
||||||
self.leftPanelButton = [panel buttonForPanel];
|
self.leftPanelButton = [panel buttonForPanel];
|
||||||
@ -751,7 +753,7 @@ CGFloat const PanelAnimationDuration = 0.2;
|
|||||||
self.rightPanelWidth = rightPanelWidth;
|
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: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: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)]) {
|
if ([panel respondsToSelector:@selector(buttonForPanel)]) {
|
||||||
self.rightPanelButton = [panel buttonForPanel];
|
self.rightPanelButton = [panel buttonForPanel];
|
||||||
@ -771,6 +773,36 @@ CGFloat const PanelAnimationDuration = 0.2;
|
|||||||
[self.view layoutIfNeeded];
|
[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
|
#pragma mark - Bottom Progress Bar
|
||||||
|
|
||||||
- (void)setBottomProgressBarProgress:(float)progress {
|
- (void)setBottomProgressBarProgress:(float)progress {
|
||||||
@ -841,9 +873,17 @@ CGFloat const PanelAnimationDuration = 0.2;
|
|||||||
self.bottomProgressBarHeightConstraint = bottomProgressHeight;
|
self.bottomProgressBarHeightConstraint = bottomProgressHeight;
|
||||||
|
|
||||||
if (topAlertView) {
|
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 {
|
} 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
|
// Cover View
|
||||||
@ -855,8 +895,8 @@ CGFloat const PanelAnimationDuration = 0.2;
|
|||||||
self.mainViewCoverView = coverView;
|
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: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: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:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view 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:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:coverView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
|
||||||
|
|
||||||
[self setupPanels];
|
[self setupPanels];
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user