Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
Tab view container. Used for profile badges
Browse files Browse the repository at this point in the history
Adds general support for multiple panes to the user profile. Still need
to actually support for any other panes.

JIRA: https://openedx.atlassian.net/browse/MA-1670
  • Loading branch information
Akiva Leffert committed Apr 7, 2016
1 parent 1610cdc commit 2b65a63
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 12 deletions.
113 changes: 113 additions & 0 deletions Source/TabContainerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// TabContainerView.swift
// edX
//
// Created by Akiva Leffert on 4/5/16.
// Copyright © 2016 edX. All rights reserved.
//

import Foundation

// Simple tab view with a segmented control at the top
class TabContainerView : UIView {

struct Item {
let name : String
let view : UIView
let identifier : String
}

private let control = UISegmentedControl()

private let stackView = TZStackView()
private var activeTabBodyView : UIView? = nil

private var currentIdentifier : String?

override init(frame: CGRect) {
super.init(frame: frame)
stackView.insertArrangedSubview(control, atIndex: 0)
stackView.axis = .Vertical
stackView.alignment = .Fill
stackView.spacing = StandardVerticalMargin

addSubview(stackView)
stackView.snp_makeConstraints {make in
make.leading.equalTo(self.snp_leadingMargin)
make.trailing.equalTo(self.snp_trailingMargin)
make.top.equalTo(self.snp_topMargin)
make.bottom.equalTo(self.snp_bottomMargin)
}

let selectedAttributes = OEXTextStyle(weight: .Normal, size: .Small, color: OEXStyles.sharedStyles().neutralBlackT())
let unselectedAttributes = OEXTextStyle(weight: .Normal, size: .Small, color: OEXStyles.sharedStyles().neutralDark())
control.setTitleTextAttributes(selectedAttributes.attributes, forState: .Selected)
control.setTitleTextAttributes(unselectedAttributes.attributes, forState: .Normal)
control.tintColor = OEXStyles.sharedStyles().primaryXLightColor()
control.oex_addAction({[weak self] control in
let index = (control as! UISegmentedControl).selectedSegmentIndex
self?.showTabAtIndex(index)
}, forEvents: .ValueChanged)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

var items : [Item] = [] {
didSet {
control.removeAllSegments()

for (index, item) in items.enumerate() {
control.insertSegmentWithTitle(item.name, atIndex: control.numberOfSegments, animated: false)
if item.identifier == currentIdentifier {
showTabAtIndex(index)
}
}
if control.selectedSegmentIndex == UISegmentedControlNoSegment && items.count > 0 {
showTabAtIndex(0)
}
else {
currentIdentifier = nil
}

if items.count < 2 {
control.hidden = true
}
}
}

private func showTabAtIndex(index: Int) {
guard index != UISegmentedControlNoSegment else {
return
}

activeTabBodyView?.removeFromSuperview()

let item = items[index]
control.selectedSegmentIndex = index
currentIdentifier = item.identifier
stackView.addArrangedSubview(item.view)
activeTabBodyView = item.view
}

private func indexOfItemWithIdentifier(identifier : String) -> Int? {
return items.firstIndexMatching {$0.identifier == identifier }
}

func showTabWithIdentifier(identifier : String) {
if let index = indexOfItemWithIdentifier(identifier) {
showTabAtIndex(index)
}
}
}

// Only used for testing
extension TabContainerView {
func t_isShowingViewForItem(item : Item) -> Bool {
let viewsMatch = stackView.arrangedSubviews == [control, item.view]
let indexMatches = indexOfItemWithIdentifier(item.identifier) == control.selectedSegmentIndex
let identifierMatches = currentIdentifier == item.identifier
return viewsMatch && indexMatches && identifierMatches
}
}
30 changes: 19 additions & 11 deletions Source/UserProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ class UserProfileView : UIView, UIScrollViewDelegate {
private let countryLabel = UILabel()
private let languageLabel = UILabel()
private let bioText = UITextView()
private let tabs = TabContainerView()
private let bioSystemMessage = SystemLabel()
private let avatarImage = ProfileImageView()
private let header = ProfileBanner()
private let bottomBackground = UIView()

override init(frame: CGRect) {
super.init(frame: frame)
bioText.textContainerInset = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 10)

self.addSubview(scrollView)
scrollView.backgroundColor = OEXStyles.sharedStyles().primaryBaseColor()

setupViews()
setupConstraints()
}

private func setupViews() {
scrollView.backgroundColor = OEXStyles.sharedStyles().primaryBaseColor()
scrollView.delegate = self

avatarImage.borderWidth = 3.0
Expand All @@ -64,19 +64,27 @@ class UserProfileView : UIView, UIScrollViewDelegate {
countryLabel.setContentHuggingPriority(1000, forAxis: .Vertical)
scrollView.addSubview(countryLabel)

bioText.backgroundColor = OEXStyles.sharedStyles().neutralWhiteT()
bioText.backgroundColor = UIColor.clearColor()
bioText.textAlignment = .Natural
bioText.scrollEnabled = false
bioText.editable = false
scrollView.addSubview(bioText)
bioText.textContainer.lineFragmentPadding = 0;
bioText.textContainerInset = UIEdgeInsetsZero

tabs.layoutMargins = UIEdgeInsets(top: StandardHorizontalMargin, left: StandardHorizontalMargin, bottom: StandardHorizontalMargin, right: StandardHorizontalMargin)

tabs.items = [
TabContainerView.Item(name: "About", view: bioText, identifier: "bio")
]
scrollView.addSubview(tabs)

bottomBackground.backgroundColor = bioText.backgroundColor
scrollView.insertSubview(bottomBackground, belowSubview: bioText)
scrollView.insertSubview(bottomBackground, belowSubview: tabs)

bioSystemMessage.hidden = true
bioSystemMessage.numberOfLines = 0
bioSystemMessage.backgroundColor = OEXStyles.sharedStyles().neutralXLight()
scrollView.insertSubview(bioSystemMessage, aboveSubview: bioText)
scrollView.insertSubview(bioSystemMessage, aboveSubview: tabs)

header.style = .LightContent
header.backgroundColor = scrollView.backgroundColor
Expand Down Expand Up @@ -121,7 +129,7 @@ class UserProfileView : UIView, UIScrollViewDelegate {
make.centerX.equalTo(scrollView)
}

bioText.snp_makeConstraints { (make) -> Void in
tabs.snp_makeConstraints { (make) -> Void in
make.top.equalTo(countryLabel.snp_bottom).offset(35).priorityHigh()
make.bottom.equalTo(scrollView)
make.leading.equalTo(scrollView)
Expand All @@ -130,11 +138,11 @@ class UserProfileView : UIView, UIScrollViewDelegate {
}

bioSystemMessage.snp_makeConstraints { (make) -> Void in
make.top.equalTo(bioText)
make.top.equalTo(tabs)
make.bottom.greaterThanOrEqualTo(self)
make.leading.equalTo(bioText)
make.trailing.equalTo(bioText)
make.width.equalTo(bioText)
make.leading.equalTo(scrollView)
make.trailing.equalTo(scrollView)
make.width.equalTo(scrollView)
}

bottomBackground.snp_makeConstraints {make in
Expand Down
54 changes: 54 additions & 0 deletions Test/TabContainerViewTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// TabContainerViewTests.swift
// edX
//
// Created by Akiva Leffert on 4/5/16.
// Copyright © 2016 edX. All rights reserved.
//

import Foundation
import XCTest

@testable import edX

class TabContainerViewTests : XCTestCase {

var testItems : (TabContainerView.Item, TabContainerView.Item) = (
TabContainerView.Item(name: "Name", view: UIView(), identifier: "item1"),
TabContainerView.Item(name: "Name", view: UIView(), identifier: "item2")
)

func testSettingItemsChoosesFirstItem() {
let tabView = TabContainerView()

let (firstItem, secondItem) = testItems
tabView.items = [firstItem, secondItem]

XCTAssertTrue(tabView.t_isShowingViewForItem(firstItem))
}

func testSettingEmptyItemsClears() {
let tabView = TabContainerView()

let (firstItem, secondItem) = testItems
tabView.items = [firstItem, secondItem]
XCTAssertTrue(tabView.t_isShowingViewForItem(firstItem))

tabView.items = []
XCTAssertTrue(!tabView.t_isShowingViewForItem(firstItem))
}

func testSwitchingTabs() {
let tabView = TabContainerView()

let (firstItem, secondItem) = testItems
tabView.items = [firstItem, secondItem]
XCTAssertTrue(tabView.t_isShowingViewForItem(firstItem))

tabView.showTabWithIdentifier(secondItem.identifier)
XCTAssertTrue(tabView.t_isShowingViewForItem(secondItem))

tabView.showTabWithIdentifier(firstItem.identifier)
XCTAssertTrue(tabView.t_isShowingViewForItem(firstItem))
}
}
18 changes: 17 additions & 1 deletion edX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@
77AF076F1CADD20F00A42704 /* BadgesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AF076E1CADD20F00A42704 /* BadgesModel.swift */; };
77AF07731CADD44A00A42704 /* BadgesAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AF07711CADD37800A42704 /* BadgesAPITests.swift */; };
77AF07751CB311B200A42704 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AF07741CB311B200A42704 /* UserProfileView.swift */; };
77AF07771CB42AC400A42704 /* TabContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AF07761CB42AC400A42704 /* TabContainerView.swift */; };
77AFD10B1C1A04C3001985FD /* UserAgentOverrideOverrideOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AFD10A1C1A04C3001985FD /* UserAgentOverrideOverrideOperation.swift */; };
77AFD10E1C1A75A5001985FD /* UserAgentGenerationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AFD10C1C1A63C3001985FD /* UserAgentGenerationOperation.swift */; };
77AFD1111C1B4F79001985FD /* NSString+TestExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 77AFD1101C1B4F79001985FD /* NSString+TestExamples.m */; };
Expand All @@ -392,6 +393,7 @@
77BECB0A1B0A8AD800894276 /* UIEdgeInsets+GeometryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77BECB091B0A8AD800894276 /* UIEdgeInsets+GeometryTests.swift */; };
77BECB0D1B0A8BBD00894276 /* UIEdgeInsets+Geometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77BECB051B0A668000894276 /* UIEdgeInsets+Geometry.swift */; };
77BFD86A1BB9E15B001D7BE5 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77BFD8691BB9E15B001D7BE5 /* Strings.swift */; };
77C6BBC81CB43A6D0026C37B /* TabContainerViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C6BBC61CB438170026C37B /* TabContainerViewTests.swift */; };
77C901A71BBDDD630016E468 /* TableCellStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C901A61BBDDD630016E468 /* TableCellStyle.swift */; };
77CBF55A1BFBE89300B4F121 /* CourseAnnouncementsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CBF5591BFBE89300B4F121 /* CourseAnnouncementsViewControllerTests.swift */; };
77D470571C11EB4D00C6F0C9 /* ChoiceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77D470561C11EB4D00C6F0C9 /* ChoiceLabel.swift */; };
Expand Down Expand Up @@ -1137,6 +1139,7 @@
77AF076E1CADD20F00A42704 /* BadgesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BadgesModel.swift; path = Source/Core/Code/BadgesModel.swift; sourceTree = SOURCE_ROOT; };
77AF07711CADD37800A42704 /* BadgesAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgesAPITests.swift; sourceTree = SOURCE_ROOT; };
77AF07741CB311B200A42704 /* UserProfileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
77AF07761CB42AC400A42704 /* TabContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabContainerView.swift; sourceTree = "<group>"; };
77AFD10A1C1A04C3001985FD /* UserAgentOverrideOverrideOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAgentOverrideOverrideOperation.swift; sourceTree = "<group>"; };
77AFD10C1C1A63C3001985FD /* UserAgentGenerationOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAgentGenerationOperation.swift; sourceTree = "<group>"; };
77AFD10F1C1B4F79001985FD /* NSString+TestExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+TestExamples.h"; sourceTree = "<group>"; };
Expand All @@ -1156,6 +1159,7 @@
77BECB071B0A771700894276 /* OfflineModeViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OfflineModeViewTests.swift; sourceTree = "<group>"; };
77BECB091B0A8AD800894276 /* UIEdgeInsets+GeometryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+GeometryTests.swift"; sourceTree = "<group>"; };
77BFD8691BB9E15B001D7BE5 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = BUILT_PRODUCTS_DIR; };
77C6BBC61CB438170026C37B /* TabContainerViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabContainerViewTests.swift; sourceTree = "<group>"; };
77C901A61BBDDD630016E468 /* TableCellStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellStyle.swift; sourceTree = "<group>"; };
77CBF5591BFBE89300B4F121 /* CourseAnnouncementsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseAnnouncementsViewControllerTests.swift; sourceTree = "<group>"; };
77D470561C11EB4D00C6F0C9 /* ChoiceLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChoiceLabel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2293,6 +2297,15 @@
name = API;
sourceTree = "<group>";
};
77AF07781CB42ACB00A42704 /* Helper Views */ = {
isa = PBXGroup;
children = (
199B9B761935EA5200081A09 /* ErrorView */,
77AF07761CB42AC400A42704 /* TabContainerView.swift */,
);
name = "Helper Views";
sourceTree = "<group>";
};
77AFD11E1C1B77D3001985FD /* Lists */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2948,9 +2961,9 @@
198826E41952FE1A005D4D8A /* Open In Browser */,
1913DD3A194B265600573977 /* Download View */,
193B504219459BE60038E11C /* Custom Styles */,
77AF07781CB42ACB00A42704 /* Helper Views */,
191A001D194055DD004F7902 /* Model */,
8FE04B4A1A1E24C7007F88B8 /* SocialLogin */,
199B9B761935EA5200081A09 /* ErrorView */,
E0AF4E8C1BFB19A00083753C /* PassthroughView */,
199B9B6419349DE800081A09 /* Videos */,
BE0D454E192DE63900D720D6 /* Data */,
Expand Down Expand Up @@ -2986,6 +2999,7 @@
BECB7B331924C0C3009C77F1 /* Unit Tests */ = {
isa = PBXGroup;
children = (
77C6BBC61CB438170026C37B /* TabContainerViewTests.swift */,
775391491C98A15100FA959C /* VideoBlockViewControllerTests.swift */,
775391401C97593B00FA959C /* NetworkManager+InterceptionTests.swift */,
77ADB7921C92228D006A66A1 /* XCTestCase+Stream.swift */,
Expand Down Expand Up @@ -3882,6 +3896,7 @@
772619B71ADDA8ED005BD7E4 /* OEXPushSettingsManager.m in Sources */,
77ADF49E1AC1ACAA00AC8955 /* OEXExternalRegistrationOptionsView.m in Sources */,
B4B6D63F1A952D1C000F44E8 /* OEXRegistrationAgreementView.m in Sources */,
77AF07771CB42AC400A42704 /* TabContainerView.swift in Sources */,
199B9B751935E35D00081A09 /* OEXHelperVideoDownload.m in Sources */,
BECB7B1F1924C0C3009C77F1 /* OEXAppDelegate.m in Sources */,
B4B6D6151A949F0F000F44E8 /* OEXRegistrationFormTextField.m in Sources */,
Expand Down Expand Up @@ -4150,6 +4165,7 @@
775391411C97593B00FA959C /* NetworkManager+InterceptionTests.swift in Sources */,
774B9C121B0B9D18000B1069 /* MockReachability.swift in Sources */,
773181401B6C23F9000CC050 /* DiscussionTopic+DataFactory.swift in Sources */,
77C6BBC81CB43A6D0026C37B /* TabContainerViewTests.swift in Sources */,
770A27A11A6F172300DFC6FF /* OEXVideoSummaryTests.m in Sources */,
77E648A51C92062900B6740D /* MockResponseCache.swift in Sources */,
77567B791ACA00F900877A7B /* NSNotificationCenter+OEXSafeAccessTests.m in Sources */,
Expand Down

0 comments on commit 2b65a63

Please sign in to comment.