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
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar.imageset/Avatar.png
поставляемый
До Ширина: | Высота: | Размер: 73 KiB |
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_allan_munger.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_allan_munger.imageset/avatar_allan_munger.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 294 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_amanda_brady.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_amanda_brady.imageset/avatar_amanda_brady.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 301 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_ashley_mccarthy.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_ashley_mccarthy.imageset/avatar_ashley_mccarthy.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 271 KiB |
|
@ -2,7 +2,7 @@
|
|||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Avatar.png",
|
||||
"filename" : "avatar_cecil_folk.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_cecil_folk.imageset/avatar_cecil_folk.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 292 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_celeste_burton.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_celeste_burton.imageset/avatar_celeste_burton.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 346 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_colin_ballinger.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_colin_ballinger.imageset/avatar_colin_ballinger.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 285 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_daisy_phillips.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_daisy_phillips.imageset/avatar_daisy_phillips.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 274 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_elvia_atkins.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_elvia_atkins.imageset/avatar_elvia_atkins.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 272 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_henry_brill.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_henry_brill.imageset/avatar_henry_brill.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 340 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_isaac_fielder.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_isaac_fielder.imageset/avatar_isaac_fielder.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 263 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_johnie_mcconnell.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_johnie_mcconnell.imageset/avatar_johnie_mcconnell.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 326 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_kat_larsson.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_kat_larsson.imageset/avatar_kat_larsson.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 335 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_katri_ahokas.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_katri_ahokas.imageset/avatar_katri_ahokas.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 308 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_lydia_bauer.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_lydia_bauer.imageset/avatar_lydia_bauer.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 314 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_mauricio_august.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_mauricio_august.imageset/avatar_mauricio_august.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 272 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_miguel_garcia.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_miguel_garcia.imageset/avatar_miguel_garcia.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 301 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_robert_tolbert.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_robert_tolbert.imageset/avatar_robert_tolbert.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 248 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_robin_counts.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_robin_counts.imageset/avatar_robin_counts.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 275 KiB |
21
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_tim_deboer.imageset/Contents.json
поставляемый
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
Двоичные данные
OfficeUIFabric.Demo/OfficeUIFabric.Demo/Assets.xcassets/Avatar/avatar_tim_deboer.imageset/avatar_tim_deboer.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 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])
|
||||
}
|
||||
}
|