Im working on a project in swift 3.0 and I have two UITableViews where I set data fetched from a core-data module entity called UserIncome. As these data will be populated in two UItableViews in a single UIViewController (filtering based on a String value in the ViewWillAppear delegate method),once a row is been deleted in one UITableView, its array automatically gets updated by the other tableView's objects too. But once I click the back button and come back to the same UIViewController all seems fine. My requirement is to update the UItableView once a row is been deleted so as the core data module. The code as bellow. What am I missing here?
import UIKit
import CoreData
class MyIncomesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var recurringIncomeTableView: UITableView!
#IBOutlet weak var otherIncomeTableView: UITableView!
//var myIncomeType : String?
var stores = [UserIncome] ()
var other = [UserIncome] ()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var rowTbl : Int!
var rowTbl2 : Int!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
stores.removeAll()
other.removeAll()
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "UserIncome")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request) as! [UserIncome]
print("Results from the fetch request are : ", request)
// check data existance
if results.count>0 {
print("results are :", results.count)
for resultGot in results {
//lets check if the data is available and whether the loop is working by printing out the "name"
if let incName = resultGot.incomeName {
print("expence name is :", incName)
//set the value to the global variable as to filter the arrays
let myIncomeType = resultGot.incomeType
if myIncomeType == "Recurring Income"{
stores += [resultGot]
print("my recurring income array is : \(stores)")
}else if myIncomeType == "Other Income"{
other += [resultGot]
print("my other income array is : \(other)")
}
}
}
self.recurringIncomeTableView.reloadData()
self.otherIncomeTableView.reloadData()
}
}catch{
print("No Data to load")
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.tag == 1 {
let cell: RecuringIncomeTableViewCell = tableView.dequeueReusableCell(withIdentifier: "recurringIncomeCell") as! RecuringIncomeTableViewCell
let store = stores [indexPath.row]
cell.incomeNameLabel.text = store.incomeName
cell.amountLabel.text = store.amount
return cell
}
else {
let cell: OtherIncomeTableViewCell = tableView.dequeueReusableCell(withIdentifier: "otherIncomeCell") as! OtherIncomeTableViewCell
let otherIncomes = other [indexPath.row]
cell.incomeNameLabel.text = otherIncomes.incomeName
cell.amountLabel.text = otherIncomes.amount
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//performSegue(withIdentifier: "editStore", sender: nil)
if tableView.tag == 1 {
rowTbl = tableView.indexPathForSelectedRow?.row
print("current row in tbl 1 is : ",rowTbl)
}else {
rowTbl2 = tableView.indexPathForSelectedRow?.row
print("current row in tbl 2 is : ",rowTbl2)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editRecurringIncome"{
let v = segue.destination as! AddIncomeViewController
let indexPath = self.recurringIncomeTableView.indexPathForSelectedRow
let row = indexPath?.row
v.store = stores[row!]
}else if segue.identifier == "editOtherIncome" {
let t = segue.destination as! AddIncomeViewController
let indexPath = self.otherIncomeTableView.indexPathForSelectedRow
let row = indexPath?.row
t.store = other [row!]
}
}
//
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
print("delete delegate being activated")
return true
}
//For remove row from tableview & object from array.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if tableView.tag == 1 {
if editingStyle == .delete {
let task = stores [indexPath.row]
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
stores = try context.fetch(UserIncome.fetchRequest())
print("Stores deleted from indexPath",stores)
}catch{
print("fail")
}
recurringIncomeTableView.reloadData()
}
self.recurringIncomeTableView.reloadData()
} else if tableView.tag == 2 {
if editingStyle == .delete {
let task = other[indexPath.row]
print("task on otherTblView is : ",task)
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
otherIncomeTableView.reloadData()
do {
other = try context.fetch(UserIncome.fetchRequest())
print("Stores deleted from indexPath",other)
}catch{
print("fail")
}
}
self.otherIncomeTableView.reloadData()
}
tableView.reloadData()
}
}
you need to delete task like this way
let task = stores [indexPath.row]
context.delete(task)
stores.removeAtIndex(indexPath.row) // i think you forget this line
(UIApplication.shared.delegate as! AppDelegate).saveContext()
try this,hope it will help you
A core data object doesn't really contain any information. It has a pointer to a context and an ID, so when you ask it for information it goes to the store to ask. If the object is deleted from the context then the manage object that you have stored in your array will no longer work and will crash. This is why you should never retain NSManagedObjects. Either
a) Copy the values from core data into an a different object. When you want to delete an object you have to delete it from both the store and the copy that you are retaining. If new objects are inserted, or they are deleted from some other source outside of you viewController it will not update (but also no crash).
b) Use a NSFetchedResultsController and update the results when the values change. This will give a delegate to tell you when changes happen. So all you have to do it delete the object from the store and then the fetchedResultsController will tell you when to remove it.
Related
I am trying to show an information which is in Core Data, on UITableViewCell.
I could get the information, but the information wasn't shown on UITableViewCell.
When I set the information on CoreData, I use Modal View then.
I tried to use UITableView.reload() but I couldn't show the information on UITableViewCell.
Please let me know how to show the information when I back from modal view.
This class is about showing the information on UItableView.
import UIKit
import CoreData
protocol FriendListTableViewDelegate {
func reloadTable()
}
class FriendListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, FriendListTableViewDelegate{
#IBOutlet weak var friendListTableView: UITableView!
var friends:[FriendBasicInfo] = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
// Disable to effect the reload
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadTable()
}
// fetch the information from CoreData
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
friends = try context.fetch(FriendBasicInfo.fetchRequest())
} catch {
print("error")
}
}
func reloadTable() {
friendListTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let friendCell = tableView.dequeueReusableCell(withIdentifier: "FriendListCell") as! FriendListTableViewCell
let friendName = friendCell.viewWithTag(1) as? UILabel
let friendImage = friendCell.viewWithTag(2) as? UIImageView
friendName?.text = friends[indexPath.row].name
friendImage?.image = friends[indexPath.row].photo?.toImage()
return friendCell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
This class is to get the information from CoreData
import UIKit
import XLPagerTabStrip
import Eureka
import CoreData
import ImageRow
class InputFriendInforViewController: FormViewController, IndicatorInfoProvider {
var itemInfo: IndicatorInfo = "Info"
var friendPhoto: UIImage?
var friendName: String = ""
var friendBirthday: Date?
var friendGender: String = ""
var friendListTableViewDelegate: FriendListTableViewDelegate!
override func viewDidLoad() {
super.viewDidLoad()
form +++
Section("Friend Information")
<<< ImageRow(){
$0.title = "Image"
$0.sourceTypes = [.PhotoLibrary, .SavedPhotosAlbum, .Camera]
$0.value = UIImage(named: "noImage")
$0.onChange { [unowned self] row in
self.friendPhoto = row.value!
}
}
<<< TextRow(){ row in
row.title = "Name"
row.placeholder = "Enter Name here"
}.onChange { name in
self.friendName = name.value!
}
<<< DateRow(){ row in
row.title = "Birthday"
row.value = Date(timeIntervalSinceReferenceDate: 0)
}.onChange {date in
self.friendBirthday = date.value!
}
<<< PushRow<String>(){row in
row.title = "Gender"
row.options = ["Male","Female","Other"]
}.onChange {gender in
self.friendGender = gender.value!
}
+++ Section()
<<< ButtonRow() {
$0.title = "SAVE"
}.onCellSelection {_, _ in
self.saveInfo()
}
}
// MARK: - IndicatorInfoProvider
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return itemInfo
}
// save friend Info for Core Data
func saveInfo (){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let friendEntity = NSEntityDescription.entity(forEntityName: "FriendBasicInfo", in: managedContext)!
let friendInfo = NSManagedObject(entity: friendEntity, insertInto: managedContext)
// make unique user ID
let friendUid = NSUUID().uuidString
// Image Data UIImage to png Data
let pngImage = self.friendPhoto?.toPNGData()
friendInfo.setValue(friendUid, forKey: "userID")
friendInfo.setValue(pngImage, forKey: "photo")
friendInfo.setValue(self.friendName, forKey: "name")
friendInfo.setValue(self.friendBirthday, forKey: "birthday")
friendInfo.setValue(self.friendGender, forKey: "gender")
do {
try managedContext.save()
self.dismiss(animated: true, completion:nil)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
This class is about UITableViewCell
import UIKit
class FriendListTableViewCell: UITableViewCell {
#IBOutlet weak var sampleImageView: UIImageView!
#IBOutlet weak var sampleLabel:UILabel!
}
V/r,
As you are using an extra data source array just reloading the table view doesn't consider the new inserted item.
There are a few options
Use NSFetchedResultsController. It updates the UI automatically when the context was saved.
On dismiss insert the new item into the data source array and a new row into the table view.
Observe NSManagedObjectContextDidSaveNotification and insert the item as described in 2.
Refetch the entire data and reload the table view.
The options are in order of efficiency. Version 1 is the most efficient one.
Side note:
viewWithTag is horribly old-fashioned. You got outlets, use them for example
cell.sampleLabel!.text = friends[indexPath.row].name
Your FriendListViewController TableView will reflect any updates to any FriendBasicInfo Entity which was fetched within getData() method. To present a new inserted FriendBasicInfo Entities to the database you have to execute a new fetch with getData() method.
Solution:
func reloadTable() {
getData()
friendListTableView.reloadData()
}
Alternative solution
Advanced monitoring of a fetched entities can be done with NSFetchedResultsController Delegate, this controller will automatically update the FriendListViewController tableview for any updated, inserted or deleted entities.
I can load my current tableview data onto the database and then print out the new data onto my console but can't get the new data back into the tableview and I'm tearing my hair out because I know it should be simple!
I've tried all sorts of things but I just can't figure out where I'm going wrong.
//Saves to database without any problems
//Class
var ref: DatabaseReference!
//ViewDidLoad
ref = Database.database().reference()
func save()
{
let ref = Database.database().reference(withPath: "Admin")
let adding = ref.child(me)
let addData: [String: [String]] = ["addJokes": data]
adding.setValue(addData)
{
(error:Error?, ref:DatabaseReference) in
if let error = error
{
print("Data could not be saved: \(error).")
}
else
{
print("Data saved successfully!")
}
}
}
Can print out the database data to my console but can't get it into my tableview
let ref = Database.database().reference(withPath: "Admin")
ref.observe(.value, with:
{
(snapshot) in
let new = snapshot.value as? String
print(snapshot.value as Any)
if let newData = new
{
self.data.append(newData)
self.mainTable.reloadData()
}
})
Update
TableView details-
TableView Class Ext
extension TableView: UITableViewDataSource, UITableViewDelegate
{
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if isSearching {
return filteredArray.count
}
else
{
return data.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var array: String?
if isSearching
{
array = filteredArray[indexPath.row]
}
else
{
array = data[indexPath.row]
}
let cell = mainTable.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as UITableViewCell
cell.textLabel?.text = array
return cell
}
TableView Class-
class TableView: UIViewController
{
let cellId = "cellId"
var filteredArray = [String]()
var ref: DatabaseReference!
var data = [
"""
multiple line
data array
"""
]
lazy var mainTable: UITableView =
{
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
mainTable.delegate = self
mainTable.dataSource = self
}
Console prints exactly what I want back into my tableview. Turning print function into results is usually the easy part.
The problem lies in let new = snapshot.value as? String. Here, new is null thus if let newData = new is always false and if block won't be executed. First, check snapshot.value's data type and value then use it accordingly.
I'm creating an e-commerce app with (Moltin.com) SDK, I set every thing well as it shown in the documentation but now I need to load multi images of single product in table view with custom cell, I set the shown code below and all I can get is a single image my app ignore load the other images view controller code is
class vc: UIViewController , UITableViewDelegate, UITableViewDataSource {
var productDict:NSDictionary?
#IBOutlet weak var tableview: UITableView!
fileprivate let MY_CELL_REUSE_IDENTIFIER = "MyCell"
fileprivate var productImages:NSArray?
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
Moltin.sharedInstance().product.listing(withParameters: productDict!.value(forKeyPath: "url.https") as! [String : Any]!, success: { (response) -> Void in
self.productImages = response?["result"] as? NSArray
self.tableview?.reloadData()
}) { (response, error) -> Void in
print("Something went wrong...")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if productImages != nil {
return productImages!.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MY_CELL_REUSE_IDENTIFIER, for: indexPath) as! MyCell
let row = (indexPath as NSIndexPath).row
let collectionDictionary = productImages?.object(at: row) as! NSDictionary
cell.setCollectionDictionary(collectionDictionary)
return cell
}
and my custom cell code is
class MyCell: UITableViewCell {
#IBOutlet weak var myImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setCollectionDictionary(_ dict: NSDictionary) {
// Set up the cell based on the values of the dictionary that we've been passed
// Extract image URL and set that too...
var imageUrl = ""
if let images = dict.value(forKey: "images") as? NSArray {
if (images.firstObject != nil) {
imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String
}
}
myImage?.sd_setImage(with: URL(string: imageUrl))
}
Can anyone show me where is the issue that doesn't let me get all the images of my product?
I'm using SWIFT 3, with XCode
In the code below you are always getting one URL from images array (firstObject).
if let images = dict.value(forKey: "images") as? NSArray {
if (images.firstObject != nil) {
imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String
}
}
myImage?.sd_setImage(with: URL(string: imageUrl))
If I understand correctly you should get every image in images array by the indexPath.row of your tableView.
For example add new parameter to method like this:
func setCollection(with dict: NSDictionary, and index: Int) {
// Set up the cell based on the values of the dictionary that we've been passed
// Extract image URL and set that too...
var imageUrlString = ""
if let images = dict.value(forKey: "images") as? Array<NSDictionary>, images.count >= index {
guard let lImageUrlString = images[index]["url.https"] else { return }
imageUrlString = lImageUrlString
}
guard let imageURL = URL(string: imageUrl) else { return }
myImage?.sd_setImage(with: imageURL)
}
Than when call this method in cellForRow just add indexPath.row to the second param.
But if you want show multiple images in one cell you should add more imageViews to the custom cell or use UICollectionView.
Just ping me if I don't understand you clear.
Im working on a project in swift 3.0 and Im fetching data from core data on to two tableViews namely;'recurringIncomeTableView', and 'otherIncomeTableView'. However when 'commit editingStyle' function is activated (once I slide the row), I can deleted the particular row in 'recurringIncomeTableView'. But when i slide a row in 'otherIncomeTableView' and pressed delete, in the line 'let task = stores [indexPath.row]' causing the problem and the app is crashing. The code as bellow.
import UIKit
import CoreData
class MyIncomesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var recurringIncomeTableView: UITableView!
#IBOutlet weak var otherIncomeTableView: UITableView!
//var myIncomeType : String?
var stores = [UserIncome] ()
var other = [UserIncome] ()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
self.recurringIncomeTableView.reloadData()
self.otherIncomeTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
stores.removeAll()
other.removeAll()
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "UserIncome")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request) as! [UserIncome]
print("Results from the fetch request are : ", request)
// check data existance
if results.count>0 {
print("results are :", results.count)
for resultGot in results {
//lets check if the data is available and whether the loop is working by printing out the "name"
if let incName = resultGot.incomeName {
print("expence name is :", incName)
//set the value to the global variable as to filter the arrays
let myIncomeType = resultGot.incomeType
if myIncomeType == "Recurring Income"{
stores += [resultGot]
print("my recurring income array is : \(stores)")
}else if myIncomeType == "Other Income"{
other += [resultGot]
print("my other income array is : \(other)")
}
}
}
self.recurringIncomeTableView.reloadData()
self.otherIncomeTableView.reloadData()
}
}catch{
print("No Data to load")
}
}
#IBAction func addIncome(sender: UIButton) {
print("Add Income Button Clicked")
performSegue(withIdentifier: "ShowAddIncomeVC", sender: nil)
// Do whatever you need when the button is pressed
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.recurringIncomeTableView {
print("recurringIncomeTableView count is ", stores.count)
return stores.count
}else {
print("otherIncomeTableView count is ", other.count)
return other.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.recurringIncomeTableView {
let cell: RecuringIncomeTableViewCell = tableView.dequeueReusableCell(withIdentifier: "recurringIncomeCell") as! RecuringIncomeTableViewCell
let store = stores [indexPath.row]
cell.incomeNameLabel.text = store.incomeName
cell.amountLabel.text = store.amount
//cell.textLabel?.text = myExpensesArray[indexPath.row]
return cell
}else {
let cell: OtherIncomeTableViewCell = tableView.dequeueReusableCell(withIdentifier: "otherIncomeCell") as! OtherIncomeTableViewCell
let otherIncomes = other [indexPath.row]
cell.incomeNameLabel.text = otherIncomes.incomeName
cell.amountLabel.text = otherIncomes.amount
//cell.textLabel?.text = myExpensesArray[indexPath.row]
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//performSegue(withIdentifier: "editStore", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editRecurringIncome"{
let v = segue.destination as! AddIncomeViewController
let indexPath = self.recurringIncomeTableView.indexPathForSelectedRow
let row = indexPath?.row
v.store = stores[row!]
}else if segue.identifier == "editOtherIncome" {
let t = segue.destination as! AddIncomeViewController
let indexPath = self.otherIncomeTableView.indexPathForSelectedRow
let row = indexPath?.row
t.store = other [row!]
}
}
//
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
//For remove row from tableview & object from array.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
**let task = stores [indexPath.row]**
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
stores = try context.fetch(UserIncome.fetchRequest())
}catch{
print("fail")
}
}
tableView.reloadData()
}
}
As per your Core data fetch request code.
You have to store core data object in your store array than & than you can delete that object directly form store array.
You need to fetch object like this :
// Initialize Fetch Request
let fetchRequest = NSFetchRequest()
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("UserIncome", inManagedObjectContext: self.managedObjectContext)
// Configure Fetch Request
fetchRequest.entity = entityDescription
store = try self.managedObjectContext.executeFetchRequest(fetchRequest)
After getting all data you have to filter your array with your requirement and display it in tableview I have just give example how to show that data in tableview.
Show your data in cell like this :
var data: NSManagedObject = store[indexPath.row] as NSManagedObject
Cell.textLabel?.text = data.valueForKeyPath("Name") as? String
Delete your data as per your code :
let task = stores [indexPath.row]
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
it will help you to understand flow of core data with tableview.
I can't seem to get this right. I want to get core data from my Database and display all in table view. Running this only displays the last ID multiple times on my table. Could someone advise what I'm doing wrong and/or possibly assist? Thanks.
import Foundation
import CoreData
extension MyFavourites {
#NSManaged var id: String?
}
-
var myFavs : [MyFavourites]?
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let freq = NSFetchRequest(entityName: "MyFavourites")
freq.returnsObjectsAsFaults = false
do {
myFavs = try context.executeFetchRequest(freq) as? [MyFavourites]
} catch _ {
myFavs = nil
}
tableView.reloadData()
}
-
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (myFavs?.count)!
}
-
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
if myFavs!.count > 0 {
for result: AnyObject in myFavs! {
if let favID: String = result.valueForKey("id") as? String {
cell.textLabel?.text = favID
}
}
} else {
print("No Record")
}
return cell
}
If I am reading your code correctly, it will display last recorded favId in all cells. The cellForRowAtIndexPath asks you for value for current cell, but instead of providing that, you loop through all of them and repeatedly assign the same label with favID rewriting it multiple times. At the end of the cycle the label will have the last ID from the list.
You need to remove the loop and assign cell.label.text with ID value from myFavs[indexPath.row].