Prática: Criando uma Extensão de Atividades Diárias do Traqt

Assista e acompanhe a prática em sala dos exercícios "Criando uma Extensão de Atividades Diárias do Traqt". Execute as instruções conforme a apresentação.

Exercício 1. Incluindo uma Extensão no Projeto

1.1. Adicionar um Target de Today Extension ao Projeto

Adicionar um Target do tipo Today Extension ao projeto com o Product Name DailyActivitiesExtension.

1.2. Alterar o Bundle Display Name da Extensão

Alterar a chave Bundle display name no arquivo Info.plist da extensão para o nome Atividades do Dia.


Exercício 2. Criando a Interface da Extensão

2.1. Abra o arquivo Main.storyboard do Grupo Daily Activities Extension

2.2. Exclua o View Controller criado por padrão

2.3. Adicione um Table View Controller ao Storyboard

2.4. Configure a Prototype Cell

Configure a Prototype Cell do View Controller de acordo com as informações abaixo:

  • Style: Right Detail
  • Identifier: ActivityCell
  • Selection: None
  • Background: Clear Color

2.5. Configure o Label do Prototype Cell

Modifique a propriedade Color do Label Title da Prototype Cell para White.

2.6. Modifique o Background Color dos Demais elementos

Modifique a propriedade Background Color no Table View e no Content View para Clear Color

2.7. Configure o Table View Controller como ponto de entrada


Exercício 3. Codificando a Extensão

3.1. Atualize o código do Arquivo TodayViewController.swift

Atualize o código do Arquivo TodayViewController.swift conforme a listagem abaixo:

class TodayViewController: UIViewController, NCWidgetProviding {
class TodayViewController: UITableViewController, NCWidgetProviding {

    //
    // MARK: - Properties



    //
    // MARK: - View Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view from its nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    //
    // MARK: - NCWidgetProviding

    func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
        // Perform any setup necessary in order to update the view.

        // If an error is encountered, use NCUpdateResult.Failed
        // If there's no update required, use NCUpdateResult.NoData
        // If there's an update, use NCUpdateResult.NewData

        completionHandler(NCUpdateResult.NewData)
    }

}

3.2. Inclua as Propriedades na Classe

Inclua as seguintes propriedades na classe:

let CELL_HEIGHT = CGFloat(44)

var activities = [
        ["Activity": "Atividade 1", "Time": "07:00"],
        ["Activity": "Atividade 2", "Time": "08:30"],
        ["Activity": "Atividade 3", "Time": "14:45"],
    ]

3.3. Inclua uma Seção de Métodos de Apoio

Inclua uma seção de métodos de apoio conforme a listagem abaixo:

//
// MARK: - Métodos de Apoio

func calculatePreferredContentSize() {
    let totalHeight = CGFloat(self.activities.count) * CELL_HEIGHT + self.tableView.sectionFooterHeight
    self.preferredContentSize = CGSize(width: 0, height: totalHeight)
}

3.4. Atualize o Código do método widgetPerformUpdateWithCompletionhandler:completionHandler:

Atualize o Código do método widgetPerformUpdateWithCompletionhandler:completionHandler: conforme a listagem abaixo:

func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
    self.calculatePreferredContentSize()
    completionHandler(NCUpdateResult.NewData)
}

3.5. Inclua os Métodos do UITableViewDataSource e UITableViewDelegate

Inclua os Métodos do UITableViewDataSource e UITableViewDelegate conforme a listagem abaixo:

//
// MARK: - UITableViewDelegate

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return CELL_HEIGHT
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("ActivityCell", forIndexPath: indexPath)

    cell.textLabel?.text = self.activities[indexPath.row]["Activity"]
    cell.detailTextLabel?.text = self.activities[indexPath.row]["Time"]
    return cell
}

3.6. Configure a propriedade Class para TodayViewController


Exercício 4. Compartilhando os Dados de Atividades

4.1. Configure o App Groups para o Target do App

Usar o App Group: group.com.catteno.traqt.extensions.

4.2. Configure o App Groups para o Target da Extension

Usar o App Group: group.com.catteno.traqt.extensions.

4.3. Crie o arquivo SharedActivity.swift

Crie o arquivo do tipo Swift File com o nome SharedActivity.swift no grupo DailyActivityExtension, selecionando os Targets do App e da Extensão.

4.4. Inclua o código do novo arquivo

Inclua o código do novo arquivo conforme a listagem abaixo:

let kSharedActivityFieldId = "ActivityId"
let kSharedActivityFieldName = "Name"
let kSharedActivityFieldDaysOfWeek = "DaysOfWeek"
let kSharedActivityFieldTimeOfDay = "TimeOfDay"

/**
    Um classe para representar as informações de uma atividade que são compartilhadas entre o Target do App e a extensão Today.
 */
class SharedActivity: NSObject {

    //
    // MARK: - Properties

    var activityId: String
    var name: String
    var daysOfWeek: NSNumber
    var timeOfDay: NSNumber
    var timeOfDayDesc: String {
        return self.timeOfDay.integerValue.timeIntToString()
    }

    //
    // MARK: - Initializers

    init(activityId: String, name: String, daysOfWeek: Int, timeOfDay: Int) {
        self.activityId = activityId
        self.name = name
        self.daysOfWeek = daysOfWeek
        self.timeOfDay = timeOfDay
        super.init()
    }

    init(info: NSDictionary) {
        self.activityId = info[kSharedActivityFieldId] as! String
        self.name = info[kSharedActivityFieldName] as! String
        self.daysOfWeek = info[kSharedActivityFieldDaysOfWeek] as! NSNumber
        self.timeOfDay = info[kSharedActivityFieldTimeOfDay] as! NSNumber
        super.init()
    }

}

4.5. Adicione os Arquivos do Grupo Extensions no Target da Extension

4.6. Adicione os Arquivos WeekDay.swift e WeekDays.swift no Target da Extension

4.7. Inclua o método de conversão no arquivo Acitvity+Reminders.swift

Inclua o método para converter uma Activity em um SharedActivity no arquivo Activity+Reminders.swift, conforme a listagem abaixo:

//
// MARK: - Métodos para gerenciar o armazenamento compartilhado

func toSharedActivityDictionary() -> NSDictionary {
    let activityData = NSMutableDictionary()
    activityData[kSharedActivityFieldId] = self.activityId
    activityData[kSharedActivityFieldName] = self.name
    activityData[kSharedActivityFieldDaysOfWeek] = self.reminderDays
    activityData[kSharedActivityFieldTimeOfDay] = self.reminderTime
    return activityData
}

4.8. Inclua o método para atualizar o armazenamento compartilhado

Inclua o método para atualizar o armazenamento compartilhado após a declaração do método anterior:

func updateSharedStorage() {
    let sharedUserDefaults = NSUserDefaults(suiteName: "group.com.syligo.traqt.extensions")!
    var activities: [NSDictionary]

    if var sharedActivities = sharedUserDefaults.objectForKey("SharedActivities") as? [NSDictionary] {
        // First search for the activity in the list to remove
        var activityIndex = -1
        for var i = 0; i < sharedActivities.count; i++ {
            let activity = SharedActivity(info: sharedActivities[i])
            if activity.activityId == self.activityId {
                activityIndex = i
                break
            }
        }
        if activityIndex >= 0 {
            sharedActivities.removeAtIndex(activityIndex)
        }

        activities = sharedActivities
    } else {
        // If no dictionary exists before create a new one
        activities = [NSDictionary]()
    }

    // Add the activity to the shared activity list
    if !deleted && reminders?.boolValue ?? false {
        activities.append(self.toSharedActivityDictionary())
    }

    sharedUserDefaults.setObject(activities, forKey: "SharedActivities")
    sharedUserDefaults.synchronize()
}

4.9. Atualize o método didSave

Atualziar o método didSave no arquivo Activity+Reminders.swift conforme a listagem abaixo:

override func didSave() {
    super.didSave()

    // Remove as notificações agendadas anteriormente
    Activity.clearNotificationsOfActivity(self.activityId)
    self.updateSharedStorage()

    // Verifica se o registro da atividade não esta sendo excluido, nesse caso o usuário não desejará mais receber notificação alguma
    if !deleted {
        scheduleLocalNotifications()
    }
}

Exercício 5. Carregando as Atividades do Dia

5.1. Atualizar as propriedades da Classe TodayViewConroller

Atualize as propriedades da Classe TodayViewController conforme o código abaixo:

//
// MARK: - Properties

let CELL_HEIGHT = CGFloat(44)

var sharedUserDefaults = NSUserDefaults(suiteName: "group.com.catteno.traqt.extensions")
var activities = [SharedActivity]()
var activities = [
        ["Activity": "Atividade 1", "Time": "07:00"],
        ["Activity": "Atividade 2", "Time": "08:30"],
        [""Activity": "Atividade 3", "Time": "14:45"],
    ]

5.2. Atualizar a implementação do método tableView:cellForRowAtIndexPath:

Atualize a implementação do método tableView:CellForRowAtIndexPath: conforme a listagem abaixo:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("ActivityCell", forIndexPath: indexPath) as UITableViewCell

    cell.textLabel?.text = self.activities[indexPath.row]["Activity"]
    cell.textLabel?.text = self.activities[indexPath.row].name
    cell.detailTextLabel?.text = self.activities[indexPath.row]["Time"]
    cell.detailTextLabel?.text = self.activities[indexPath.row].timeOfDayDesc
    return cell
}

5.3. Incluir o método de apoio para Recarregar os Dados

Inclua o método de apoio conforme a listagem:

func refreshData() {
    self.activities.removeAll(keepCapacity: true)
    if let sharedActivities = sharedUserDefaults?.objectForKey("SharedActivities") as? [NSDictionary] {
        let refTime = NSDate().toIntTime()
        for activityDictionary in sharedActivities {
            let activity = SharedActivity(info: activityDictionary)
            let weekDays = WeekDays(rawValue: activity.daysOfWeek.integerValue)

            // Filter by time of the day and day of the week
            if activity.timeOfDay.integerValue >= refTime && (weekDays.contains(NSDate().weekDay.asWeekDays())) {
                activities.append(activity)
            }
        }
    }
}

5.4. Atualizar a implementação do método widgetPerformUpdateWithCompletionHandler:

Atualize a implementação do método widgetPerformUpdateWithCompletionHandler: conforme a listagem abaixo, exclua o código:

self.calculatePreferredContentSize()
completionHandler(NCUpdateResult.NewData)

E inclua o seguinte código:

  self.refreshData()
  if activities.count > 0 {
      completionHandler(NCUpdateResult.NewData)

      // Remove o Label informando a ausência de atividades
      self.tableView.tableHeaderView = nil
      self.calculatePreferredContentSize()
      self.tableView.reloadData()
  } else {
      completionHandler(NCUpdateResult.NoData)

      // Adiciona uma Label informando que não há atividades
      let label = UILabel()
      label.textColor = UIColor.whiteColor()
      label.text = "Nenhuma atividade pendente."
      label.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: label.intrinsicContentSize().height + 20)
      self.tableView.tableHeaderView = label
      self.preferredContentSize = CGSize(width: 0, height: label.intrinsicContentSize().height + 20)
}