Merged PR 189085: MSPersonaCell and MSPersonaListView

This PR introduces MSPersonaListView to display a list of personas. The MSPersonaListView displays a MSPersonaCell for each MSPersona in an array used as the data source to populate the list view.
- Fixes an issue where the MSAvatarView initials view background color would change on selection of the cell to the selection state background color (backgroundGray).
- Includes MSPersona protocol with properties needed to set up the MSPeopleCell and MSAvatarView to display a persona.
- Includes a personaSelected callback for when a cell is selected. This will be used to add tokens to the tokenizable textfield in the upcoming people picker control.
- Includes keyboard shortcut support to use a keyboard to navigate the table view which will be implemented in the upcoming people picker view controller.
- Demo included that shows an alert demonstrating the selection behavior of the cell.

![Screen Shot 2018-10-15 at 2.57.34 PM.png](https://onedrive.visualstudio.com/4dcbf0bc-c3cd-49c8-a7c3-ec1924691d9b/_apis/git/repositories/93ac71ee-b53a-4fc6-a8c4-d46a80d4ca39/pullRequests/189085/attachments/Screen%20Shot%202018-10-15%20at%202.57.34%20PM.png)

Related work items: #627341
This commit is contained in:
Phil Worthington 2018-10-18 23:27:49 +00:00
Родитель 4b7d6e1618
Коммит 0a21eb0ed0
49 изменённых файлов: 745 добавлений и 3 удалений

Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar.imageset/Avatar.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 73 KiB

Просмотреть файл

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 294 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 301 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 271 KiB

Просмотреть файл

@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "Avatar.png",
"filename" : "avatar_cecil_folk.png",
"scale" : "1x"
},
{

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 292 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 346 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 285 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 274 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 272 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 340 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 263 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 326 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 335 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 308 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 314 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 272 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 301 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 248 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 275 KiB

Просмотреть файл

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

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 276 KiB

Просмотреть файл

@ -9,5 +9,6 @@ let demos: [(title: String, controllerClass: UIViewController.Type)] = [
("MSAvatarView", MSAvatarViewDemoController.self),
("MSButton", MSButtonDemoController.self),
("MSDrawerController", MSDrawerDemoController.self),
("MSLabel", MSLabelDemoController.self)
("MSLabel", MSLabelDemoController.self),
("MSPersonaListView", MSPersonaListViewDemoController.self)
]

Просмотреть файл

@ -9,7 +9,7 @@ class MSAvatarViewDemoController: DemoController {
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "Avatar")
let image = UIImage(named: "avatar_kat_larsson")
addTitle(text: "Avatar with Image")
for size in MSAvatarSize.allCases {
addAvatar(size: size, image: image)

Просмотреть файл

@ -0,0 +1,57 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import OfficeUIFabric
import UIKit
class MSPersonaListViewDemoController: DemoController {
override func viewDidLoad() {
super.viewDidLoad()
let personaListView = MSPersonaListView()
personaListView.personaList = personas()
personaListView.onPersonaSelected = { [unowned self] persona in
let name = !persona.name.isEmpty ? persona.name : persona.email
let alert = UIAlertController(title: "\(name) selected", message: nil, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true)
}
view.addSubview(personaListView)
personaListView.fitIntoSuperview()
}
private func personas() -> [MSPersonaData] {
return [
MSPersonaData(name: "Kat Larrson", email: "kat.larrson@contoso.com", subtitle: "Designer", avatarImage: UIImage(named: "avatar_kat_larsson")),
MSPersonaData(name: "Kristin Patterson", email: "kristin.patterson@contoso.com", subtitle: "Software Engineer"),
MSPersonaData(name: "Erik Nason", subtitle: "Designer"),
MSPersonaData(name: "Ashley McCarthy", avatarImage: UIImage(named: "avatar_ashley_mccarthy")),
MSPersonaData(name: "Carole Poland", email: "carole.poland@contoso.com", subtitle: "Software Engineer"),
MSPersonaData(name: "Allan Munger", email: "allan.munger@contoso.com", subtitle: "Designer", avatarImage: UIImage(named: "avatar_allan_munger")),
MSPersonaData(name: "Amanda Brady", subtitle: "Program Manager", avatarImage: UIImage(named: "avatar_amanda_brady")),
MSPersonaData(name: "Kevin Sturgis", email: "kevin.sturgis@contoso.com", subtitle: "Software Engineeer"),
MSPersonaData(name: "Lydia Bauer", email: "lydia.bauer@contoso.com", avatarImage: UIImage(named: "avatar_lydia_bauer")),
MSPersonaData(name: "Robin Counts", subtitle: "Program Manager", avatarImage: UIImage(named: "avatar_robin_counts")),
MSPersonaData(name: "Tim Deboer", email: "tim.deboer@contoso.com", subtitle: "Designer", avatarImage: UIImage(named: "avatar_tim_deboer")),
MSPersonaData(email: "wanda.howard@contoso.com", subtitle: "Director"),
MSPersonaData(name: "Daisy Phillips", email: "daisy.phillips@contoso.com", subtitle: "Software Engineer", avatarImage: UIImage(named: "avatar_daisy_phillips")),
MSPersonaData(name: "Katri Ahokas", subtitle: "Program Manager", avatarImage: UIImage(named: "avatar_katri_ahokas")),
MSPersonaData(name: "Colin Ballinger", email: "colin.ballinger@contoso.com", subtitle: "Software Engineer", avatarImage: UIImage(named: "avatar_colin_ballinger")),
MSPersonaData(name: "Celeste Burton", email: "celeste.burton@contoso.com", subtitle: "Program Manager", avatarImage: UIImage(named: "avatar_celeste_burton")),
MSPersonaData(name: "Mona Kane", email: "mona.kane@contoso.com", subtitle: "Designer"),
MSPersonaData(name: "Elvia Atkins", email: "elvia.atkins@contoso.com", subtitle: "Software Engineer", avatarImage: UIImage(named: "avatar_elvia_atkins")),
MSPersonaData(name: "Johnie McConnell", subtitle: "Designer", avatarImage: UIImage(named: "avatar_johnie_mcconnell")),
MSPersonaData(name: "Charlotte Waltsson", email: "charlotte.waltsson@contoso.com", subtitle: "Software Engineer"),
MSPersonaData(name: "Mauricio August", email: "mauricio.august@contoso.com", subtitle: "Program Manager", avatarImage: UIImage(named: "avatar_mauricio_august")),
MSPersonaData(name: "Miguel Garcia", email: "miguel.garcia@contoso.com", subtitle: "Software Engineer", avatarImage: UIImage(named: "avatar_miguel_garcia")),
MSPersonaData(name: "Robert Tolbert", email: "robert.tolbert@contoso.com", subtitle: "Software Engineer", avatarImage: UIImage(named: "avatar_robert_tolbert")),
MSPersonaData(name: "Isaac Fielder", subtitle: "Designer", avatarImage: UIImage(named: "avatar_isaac_fielder")),
MSPersonaData(name: "Elliot Woodward", subtitle: "Designer"),
MSPersonaData(email: "carlos.slattery@contoso.com", subtitle: "Software Engineer"),
MSPersonaData(name: "Henry Brill", subtitle: "Software Engineer", avatarImage: UIImage(named: "avatar_henry_brill")),
MSPersonaData(name: "Cecil Folk", subtitle: "Program Manager", avatarImage: UIImage(named: "avatar_cecil_folk"))
]
}
}

Просмотреть файл

@ -33,6 +33,9 @@
B4266140214852B400E25423 /* NSString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B426613F214852B400E25423 /* NSString+Extension.swift */; };
B42661422148568800E25423 /* MSAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B42661412148568800E25423 /* MSAvatarView.swift */; };
B46D3F91215056940029772C /* CharacterSet+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46D3F90215056940029772C /* CharacterSet+Extension.swift */; };
B46D3F932151D95F0029772C /* MSPersonaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46D3F922151D95F0029772C /* MSPersonaCell.swift */; };
B46D3F9D215985AC0029772C /* MSPersonaListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46D3F9C215985AC0029772C /* MSPersonaListView.swift */; };
B4EF53C3215AF1AB00573E8F /* MSPersona.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EF53C2215AF1AB00573E8F /* MSPersona.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -75,6 +78,9 @@
B426613F214852B400E25423 /* NSString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSString+Extension.swift"; sourceTree = "<group>"; };
B42661412148568800E25423 /* MSAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSAvatarView.swift; sourceTree = "<group>"; };
B46D3F90215056940029772C /* CharacterSet+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Extension.swift"; sourceTree = "<group>"; };
B46D3F922151D95F0029772C /* MSPersonaCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSPersonaCell.swift; sourceTree = "<group>"; };
B46D3F9C215985AC0029772C /* MSPersonaListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSPersonaListView.swift; sourceTree = "<group>"; };
B4EF53C2215AF1AB00573E8F /* MSPersona.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSPersona.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -215,6 +221,9 @@
children = (
B42661412148568800E25423 /* MSAvatarView.swift */,
B426613D214731D100E25423 /* MSInitialsView.swift */,
B46D3F922151D95F0029772C /* MSPersonaCell.swift */,
B46D3F9C215985AC0029772C /* MSPersonaListView.swift */,
B4EF53C2215AF1AB00573E8F /* MSPersona.swift */,
);
path = "People Picker";
sourceTree = "<group>";
@ -339,17 +348,20 @@
B426613E214731D100E25423 /* MSInitialsView.swift in Sources */,
A5B87B02211E20B50038C37C /* UIScreen+Extension.swift in Sources */,
A5B87AEF211BCA710038C37C /* UIFontDescriptor+Extension.swift in Sources */,
B4EF53C3215AF1AB00573E8F /* MSPersona.swift in Sources */,
A5B87B06211E23650038C37C /* UIView+Extensions.swift in Sources */,
B42661422148568800E25423 /* MSAvatarView.swift in Sources */,
A589F854211BA03200471C23 /* MSLabel.swift in Sources */,
B4266140214852B400E25423 /* NSString+Extension.swift in Sources */,
A5B87AF7211E16370038C37C /* MSDrawerTransitionAnimator.swift in Sources */,
B46D3F932151D95F0029772C /* MSPersonaCell.swift in Sources */,
A5CEC16F20D98F340016922A /* Fonts.swift in Sources */,
A5B87B04211E22B70038C37C /* MSDimmingView.swift in Sources */,
A5CEC23120E451D00016922A /* MSButton.swift in Sources */,
A5B87AF6211E16370038C37C /* MSDrawerController.swift in Sources */,
A5CEC16D20D98EE70016922A /* Colors.swift in Sources */,
A559BB7E212B6D100055E107 /* String+Extension.swift in Sources */,
B46D3F9D215985AC0029772C /* MSPersonaListView.swift in Sources */,
A5DCA76421224026005F4CB7 /* MSSeparator.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

Просмотреть файл

@ -71,6 +71,10 @@ public struct MSColors {
public static let avatarText: UIColor = white
public static let background: UIColor = white
public static let personaName: UIColor = black
public static let personaSubtitle: UIColor = gray
public static let personaBackground: UIColor = white
public static let personaSelectedBackground: UIColor = backgroundGray
public static let separator: UIColor = borderLightGray
}

Просмотреть файл

@ -84,6 +84,11 @@ open class MSAvatarView: UIView {
initialsView.avatarSize = avatarSize
}
}
open var avatarBackgroundColor: UIColor {
didSet {
initialsView.backgroundColor = avatarBackgroundColor
}
}
private var name: String?
private var email: String?
private var initialsView: MSInitialsView
@ -98,6 +103,7 @@ open class MSAvatarView: UIView {
/// - hasBorder: Boolean describing whether or not to show a border around the avatarView
@objc public init(avatarSize: MSAvatarSize, withBorder hasBorder: Bool = false) {
self.avatarSize = avatarSize
avatarBackgroundColor = UIColor.clear
initialsView = MSInitialsView(avatarSize: avatarSize)
initialsView.isHidden = true
@ -177,6 +183,9 @@ open class MSAvatarView: UIView {
initialsView.setup(withName: name, email: email)
initialsView.isHidden = false
imageView.isHidden = true
if let initialsViewBackgroundColor = initialsView.backgroundColor {
avatarBackgroundColor = initialsViewBackgroundColor
}
}
private func setupWithImage(_ image: UIImage, animated: Bool = false) {

Просмотреть файл

@ -0,0 +1,28 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
// MARK: MSPersona
@objc public protocol MSPersona {
var avatarImage: UIImage? { get }
var email: String { get }
var name: String { get }
var subtitle: String { get }
}
// MARK: - MSPersonaData
@objc open class MSPersonaData: NSObject, MSPersona {
public var avatarImage: UIImage?
public var email: String
public var name: String
public var subtitle: String
@objc public init(name: String = "", email: String = "", subtitle: String = "", avatarImage: UIImage? = nil) {
self.name = name
self.email = email
self.subtitle = subtitle
self.avatarImage = avatarImage
}
}

Просмотреть файл

@ -0,0 +1,128 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import UIKit
// MARK: MSPersonaCell
open class MSPersonaCell: UITableViewCell {
private struct Constants {
static let avatarMarginLeft: CGFloat = 16
static let avatarMarginRight: CGFloat = 12
static let avatarSize: MSAvatarSize = .xLarge
static let interLabelMargin: CGFloat = 2
static let nameMarginRight: CGFloat = 16
}
public static let defaultHeight: CGFloat = 60
public static let identifier = "MSPersonaCell"
public static var separatorLeftInset: CGFloat {
return Constants.avatarMarginLeft + Constants.avatarSize.size.width + Constants.avatarMarginRight
}
private let avatarView = MSAvatarView(avatarSize: Constants.avatarSize)
private let nameLabel: UILabel = {
let label = UILabel()
label.font = MSFonts.body
label.textColor = MSColors.personaName
return label
}()
private let subtitleLabel: UILabel = {
let label = UILabel()
label.font = MSFonts.footnote
label.textColor = MSColors.personaSubtitle
label.isHidden = true
return label
}()
@objc override public init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
contentView.addSubview(avatarView)
contentView.addSubview(nameLabel)
contentView.addSubview(subtitleLabel)
setupCellBackgroundColors()
nameLabel.lineBreakMode = .byTruncatingTail
subtitleLabel.lineBreakMode = .byTruncatingTail
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Sets up the cell with an MSPersona
///
/// - Parameter persona: The MSPersona to set up the cell with
open func setup(persona: MSPersona) {
avatarView.setup(withName: persona.name, email: persona.email, image: persona.avatarImage)
// Attempt to use email if name is empty
nameLabel.text = !persona.name.isEmpty ? persona.name : persona.email
if persona.subtitle != "" {
subtitleLabel.text = persona.subtitle
subtitleLabel.isHidden = false
}
setNeedsLayout()
}
override open func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = nil
subtitleLabel.text = nil
subtitleLabel.isHidden = true
}
override open func layoutSubviews() {
super.layoutSubviews()
avatarView.left = Constants.avatarMarginLeft
avatarView.top = (contentView.height - avatarView.height) / 2
let nameHeight = ceil(nameLabel.font.lineHeight)
let subtitleHeight = ceil(subtitleLabel.font.lineHeight)
var textAreaHeight = nameHeight
if !subtitleLabel.isHidden {
textAreaHeight += subtitleHeight + Constants.interLabelMargin
}
let nameYOffset = UIScreen.main.roundToDevicePixels((contentView.height - textAreaHeight) / 2)
let nameXOffset = avatarView.right + Constants.avatarMarginRight
nameLabel.frame = CGRect(
x: nameXOffset,
y: nameYOffset,
width: contentView.width - (nameXOffset + Constants.nameMarginRight),
height: nameHeight
)
if !subtitleLabel.isHidden {
subtitleLabel.frame = CGRect(
x: nameLabel.left,
y: nameLabel.bottom + Constants.interLabelMargin,
width: nameLabel.width,
height: subtitleHeight
)
}
}
// setHighlighted and setSelected maintain the correct background color of avatar when the cell is highlighted or selected.
// Without this avatar's background color will change to the cell's background color.
override open func setHighlighted(_ highlighted: Bool, animated: Bool) {
let avatarBackgroundColor = avatarView.avatarBackgroundColor
super.setHighlighted(highlighted, animated: animated)
avatarView.avatarBackgroundColor = avatarBackgroundColor
}
override open func setSelected(_ selected: Bool, animated: Bool) {
let avatarBackgroundColor = avatarView.avatarBackgroundColor
super.setSelected(selected, animated: animated)
avatarView.avatarBackgroundColor = avatarBackgroundColor
}
private func setupCellBackgroundColors() {
backgroundColor = MSColors.personaBackground
let selectedStateBackgroundView = UIView()
selectedStateBackgroundView.backgroundColor = MSColors.personaSelectedBackground
selectedBackgroundView = selectedStateBackgroundView
}
}

Просмотреть файл

@ -0,0 +1,119 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import UIKit
// MARK: MSPersonaListViewSelectionDirection
@objc public enum MSPersonaListViewSelectionDirection: Int {
case next = 1
case prev = -1
}
// MARK: - MSPersonaListView
open class MSPersonaListView: UITableView {
/// The personas to display in the list view
open var personaList: [MSPersona] = [] {
didSet {
reloadData()
}
}
/// Callback with the selected MSPersona
@objc open var onPersonaSelected: ((MSPersona) -> Void)?
@objc override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
backgroundColor = MSColors.background
separatorColor = MSColors.separator
separatorInset = UIEdgeInsets(top: 0, left: MSPersonaCell.separatorLeftInset, bottom: 0, right: 0)
tableFooterView = UIView(frame: .zero)
register(MSPersonaCell.self, forCellReuseIdentifier: MSPersonaCell.identifier)
// Keep the cell layout margins fixed
cellLayoutMarginsFollowReadableWidth = false
dataSource = self
delegate = self
}
@objc public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Keyboard shortcut support
/// Selects a persona using the index path for selected row
@objc public func pickPersona() {
if personaList.isEmpty {
return
}
guard let indexPathToPick = indexPathForSelectedRow else {
return
}
delegate?.tableView?(self, didSelectRowAt: indexPathToPick)
}
/// Selects / deselects a row based on the MSPersonaListViewSelectionDirection value
///
/// - Parameter direction: The MSPersonaListViewSelectionDirection value to select a 'next' or 'previous' cell
@objc public func selectPersona(direction: MSPersonaListViewSelectionDirection) {
if personaList.isEmpty {
return
}
let newIndexPath: IndexPath
if let oldIndexPath = indexPathForSelectedRow {
deselectRow(at: oldIndexPath, animated: false)
reloadRows(at: [oldIndexPath], with: .none)
newIndexPath = indexPath(for: oldIndexPath, direction: direction)
} else {
newIndexPath = IndexPath(row: 0, section: 0)
}
selectRow(at: newIndexPath, animated: false, scrollPosition: .none)
}
private func indexPath(for indexPath: IndexPath, direction: MSPersonaListViewSelectionDirection) -> IndexPath {
let newRow = indexPath.row + direction.rawValue
if newRow < 0 || newRow >= personaList.count {
return indexPath
}
return IndexPath(row: newRow, section: indexPath.section)
}
}
// MARK: - UITableViewDataSource
extension MSPersonaListView: UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return personaList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = dequeueReusableCell(withIdentifier: MSPersonaCell.identifier, for: indexPath) as! MSPersonaCell
let persona = personaList[indexPath.row]
cell.setup(persona: persona)
cell.accessibilityTraits = UIAccessibilityTraitButton
return cell
}
}
// MARK: - UITableViewDelegate
extension MSPersonaListView: UITableViewDelegate {
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return MSPersonaCell.defaultHeight
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Deselecting row affects voice over focus, calling `deselectRowAtIndexPath` before `onPersonaSelected` would help.
tableView.deselectRow(at: indexPath, animated: false)
onPersonaSelected?(personaList[indexPath.row])
}
}