120课:如何实现用户之间的相互关注

课程笔记文集地址:Udemy课程:The Complete iOS 9 Developer Course - Build 18 Apps

Section 8 主要的内容是克隆 Instagram:107 - 128课。

本节课学习:用户注册之后可以相互关注。

一、前期准备

1.假数据:
首先运行上节课的工程,在模拟器里注册几个用户,这样才能实现相互关注。

2.创建新界面:
在 Storyboard 拖入 Navigation Controller,作为用户注册登录之后出现的界面。在 Storyboard 中点击黄点拖到下一个界面然后设置 Segue 的 Identifier,代码中使用 performSegueWithIdentifier("login", sender: self)

3.记住用户登录信息:
这是需求非常常见,如果每次打开App都要登录一次,用户估计就会发飙了。还好 LeanCloud 给了一个非常给力的方法,如下:

    override func viewDidAppear(animated: Bool) {        
        if AVUser.currentUser() != nil {            
            self.performSegueWithIdentifier("login", sender: self)            
        }     
    }

二、用户列表

1.新建类文件
刚刚拖入 Navigation Controller 时,自己带着一个 TableViewController,所以要有对应的类文件,新建->关联。

2.输入cell identifier
比如:cell

3.创建变量
用户名字,用户唯一的 ID。用户自己设置的昵称是有可能重复的,所以最好使用 Lean�Cloud 给创建的唯一 ID。

var usernames = [""]
var userids = [""]

这样创建数组会导致数组里的第一个元素是空字符串,我们会在之后想办法去掉。

4.从 LeanCloud 服务器下载数据
这一部分在之前学过了。

override func viewDidLoad() {
    super.viewDidLoad()
    let query = AVUser.query()
    query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
        if let users = objects {
            for object in users {
                if let user = object as? AVUser {
                    self.usernames.append(user.username!)
                    self.userids.append(user.objectId!)
                }
            }
        }
    })
}

5.去掉数组里第一个元素

self.usernames.removeAll(keepCapacity: true)
self.userids.removeAll(keepCapacity: true)

恩,至于为什么不用 removeAll() ,而用 removeAll(keepCapacity:),还是菜鸟水平的我,目前回答不了。要是哪天我整明白了,再来这里更新。

override func viewDidLoad() {
    super.viewDidLoad()
    let query = AVUser.query()
    query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
        if let users = objects {

            // 放到这里来
            self.usernames.removeAll(keepCapacity: true)
            self.userids.removeAll(keepCapacity: true)

            for object in users {
                if let user = object as? AVUser {
                    self.usernames.append(user.username!)
                    self.userids.append(user.objectId!)
                }
            }
        }
    })
}

6.tableview 的 datasource 里两个必须实现的方法

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return usernames.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) 
        cell.textLabel?.text = usernames[indexPath.row]
        return cell
    }

7.下载数据后更新 tableview 显示内容
只需要一行代码即可:tableView.reloadData()

位置如下:

override func viewDidLoad() {
   super.viewDidLoad()
   let query = AVUser.query()
   query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
       if let users = objects {
           self.usernames.removeAll(keepCapacity: true)
           self.userids.removeAll(keepCapacity: true)
           for object in users {
               if let user = object as? AVUser {
                   self.usernames.append(user.username!)
                   self.userids.append(user.objectId!)
               }
           }
       }
       // 添加到这个地方即可
       self.tableView.reloadData()
   })
}

到这里,tableview 中显示出了数据库里所有的用户。

你可能说,怎么能显示所有的用户呢,Instagram里没有显示所有的用户啊。其实既然是模仿 Instagram,那么这里可以不用这么较真的,毕竟你的服务器里只有你刚刚创建的这几个用户啊,为了开发互相关注功能,还是全部下载下来好了。

这里还有一个小问题,就是 tableview 显示的这个所有用户清单里,竟然还有你自己。。。。可是自己不能关注自己吧,所以,下一步就是解决这个问题。

8.用户表里不显示自己
加一个判断,把自己排除出去。

if user.objectId! != AVUser.currentUser()?.objectId {
}

位置如下:

override func viewDidLoad() {
   super.viewDidLoad()
   let query = AVUser.query()
   query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
       if let users = objects {
           self.usernames.removeAll(keepCapacity: true)
           self.userids.removeAll(keepCapacity: true)
           for object in users {
               if let user = object as? AVUser {
                   //把判断加到这个地方
                   if user.objectId! != AVUser.currentUser()?.objectId {
                       self.usernames.append(user.username!)
                       self.userids.append(user.objectId!)
                   }                   
               }
           }
       }
       self.tableView.reloadData()
   })
}

三、关注其他用户

1.在 LeanCloud 上如何存储关注和取消关注的信息
老师使用的方法是创建一个新的类,在 LeanCloud 控制台直接创建一个新的 Class:followers,然后在 followers 类里添加两个新的列:following(String) 和 follower(String)。

2.点击关注
这里的需求设计比较简单,点击某一行,这用户就被我关注了,为了显示出已经关注和没有关注之间的不同,用对号表示,已经关注的用户后面有一个对号。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
    cell.accessoryType = UITableViewCellAccessoryType.Checkmark
    let following = AVObject(className:"followers")
    following.setObject(userids[indexPath.row], forKey: "following")
    following.setObject(AVUser.currentUser()?.objectId, forKey: "follower")
    following.saveInBackground()
}

这时,点击某行,你就关注了这个用户了。

不过再次运行App,这个信息却没有显示出来,虽然 LeanCloud 服务器上已经记录你关注了两个用户。接下来就解决这个显示的问题。

3.读取已经关注的用户
首先,界面加载后要把关注信息从服务器里下载下来,然后再去显示出来。

下载的方法和下载所有的用户方法相似,不过多了一个筛选条件:

let query = AVQuery(className: "followers")
query.whereKey("follower", equalTo: AVUser.currentUser()!.objectId!)
query.whereKey("following", equalTo: user.objectId!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
    if let objects = objects {

    }
})

具体位置在这里:

override func viewDidLoad() {
   super.viewDidLoad()
   let query = AVUser.query()
   query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
       if let users = objects {
           self.usernames.removeAll(keepCapacity: true)
           self.userids.removeAll(keepCapacity: true)
           for object in users {
               if let user = object as? AVUser {
                   if user.objectId! != AVUser.currentUser()?.objectId {
                       self.usernames.append(user.username!)
                       self.userids.append(user.objectId!)
                       //在这里添加上面的那些代码
                       let query = AVQuery(className: "followers")
                       query.whereKey("follower", equalTo: AVUser.currentUser()!.objectId!)
                       query.whereKey("following", equalTo: user.objectId!)
                       query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
                           if let objects = objects {
                               //接下来主要在这个地方进行后续操作
                           }
                       })
                   }                   
               }
           }
       }
       self.tableView.reloadData()
   })
}

4.创建数组变量
数组的类型是词典:

var isFollowing = ["":false]

记得每次查询都要清空数组:

self.isFollowing.removeAll(keepCapacity: true)

5.查询结果存储到数组变量中

let query = AVQuery(className: "followers")
query.whereKey("follower", equalTo: AVUser.currentUser()!.objectId!)
query.whereKey("following", equalTo: user.objectId!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
    if let objects = objects {
        if objects.count > 0 {
           self.isFollowing[user.objectId!] = true
        } else {
           self.isFollowing[user.objectId!] = false
        }
    }
})

6.bug:out of range
解决这个bug的方法:

if self.isFollowing.count == self.usernames.count {
    self.tableView.reloadData()
}

最后的效果:

override func viewDidLoad() {
   super.viewDidLoad()
   let query = AVUser.query()
   query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
       if let users = objects {
           self.usernames.removeAll(keepCapacity: true)
           self.userids.removeAll(keepCapacity: true)
           self.isFollowing.removeAll(keepCapacity: true)
           for object in users {
               if let user = object as? AVUser {
                   if user.objectId! != AVUser.currentUser()?.objectId {
                       self.usernames.append(user.username!)
                       self.userids.append(user.objectId!)
                       let query = AVQuery(className: "followers")
                       query.whereKey("follower", equalTo: AVUser.currentUser()!.objectId!)
                       query.whereKey("following", equalTo: user.objectId!)
                       query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
                           if let objects = objects {
                               if objects.count > 0 {
                                  self.isFollowing[user.objectId!] = true
                               } else {
                                  self.isFollowing[user.objectId!] = false
                               }                                
                           }
                           if self.isFollowing.count == self.usernames.count {
                               self.tableView.reloadData()
                           }
                       })
                   }                   
               }
           }
       }
       self.tableView.reloadData()
   })
}

7.显示出已经关注的用户

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) 
        cell.textLabel?.text = usernames[indexPath.row]        
        let followedObjectId = userids[indexPath.row]
        
        // 添加这行代码
        if isFollowing[followedObjectId] == true {
            cell.accessoryType = UITableViewCellAccessoryType.Checkmark
        }
        return cell
    }

四、取消关注

在这里用了一个最简单粗暴的方法,点击一下,关注,再点击一下取消。

下面是目前 didSelectRowAtIndexPath 方法的现状:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
    cell.accessoryType = UITableViewCellAccessoryType.Checkmark
    let following = AVObject(className:"followers")
    following.setObject(userids[indexPath.row], forKey: "following")
    following.setObject(AVUser.currentUser()?.objectId, forKey: "follower")
    following.saveInBackground()
}

我们就要在这上面的代码的基础上进一步扩展。

1.声明一个本地变量来存储记录当前用户点击的是哪一行:

let followedObjectId = userids[indexPath.row]

2.判断所点击的这行用户是否已经关注
然后判断这一行是否已经关注了,如果已经关注,这次点击会进行取消关注的操作,如果没有关注,这次点击会进行关注的操作:

if isFollowing[followedObjectId] == false {
    //这里进行关注用户的操作
} else {
    //这里进行取消关注的操作
}

3.完成判断里的方法

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {        
        let cell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!        
        let followedObjectId = userids[indexPath.row]
        
        if isFollowing[followedObjectId] == false {            
            isFollowing[followedObjectId] = true        
            cell.accessoryType = UITableViewCellAccessoryType.Checkmark        
            let following = AVObject(className: "followers")
            following["following"] = userids[indexPath.row]
            following["follower"] = PFUser.currentUser()?.objectId        
            following.saveInBackground()            
        } else {            
            isFollowing[followedObjectId] = false          
            //取消关注,勾号消失 
            cell.accessoryType = UITableViewCellAccessoryType.None           
            let query = AVQuery(className: "followers")           
            query.whereKey("follower", equalTo: AVUser.currentUser()!.objectId!)
            query.whereKey("following", equalTo: userids[indexPath.row])         
            query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in                
                if let objects = objects {                    
                    for object in objects { 
                        //删除该条记录,也就是不再关注了                       
                        object.deleteInBackground()                        
                    }
                }                                
            })            
        }        
}

五、结束

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容