构建类 Instagram 应用全解析
1. 核心代码逻辑与内存管理
在构建应用时,部分代码涉及到视图控制器的过渡效果设置。例如:
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
picker.view.layer.add(transition, forKey: nil)
picker.pushViewController(viewController, animated: false)
此代码为视图控制器的过渡添加了淡入淡出效果。同时,在闭包中使用
unowned
和
weak
关键字来避免内存泄漏。如在闭包中,
picker
使用
unowned
,因为它会被初始化,而
self
使用
weak
,防止产生引用循环。
2. 数据模型的重要性
数据模型抽象层对于保持数据的结构化和组织化至关重要。若省略该层,直接将数据存储在字典对象中,长期来看会带来问题。例如,当后端数据库结构发生变化时,没有模型层的应用将需要进行大量更新。
以下是一个简单的数据模型
PostModel
:
class PostModel {
var photoURL:String
var description:String
var author:String
var width:Int = 0
var height:Int = 0
init(photoURL: String, description: String, author:String,
width: Int, height: Int) {
self.photoURL = photoURL
self.description = description
self.author = author
self.width = width
self.height = height
}
var toDict:[String:Any] {
var dict:[String:Any] = [:]
dict["description"] = description
dict["author"] = author
dict["width"] = width
dict["height"] = height
if let photoURL = self.photoURL {
dict["photo"] = photoURL
}
return dict
}
}
该类将帖子的相关信息封装起来,并提供了将其转换为字典的方法,方便数据的存储和传输。
3. 数据管理类
数据管理类
DataManager
负责与数据库的交互:
final class DataManager {
//private constructor
private init() {
databaseRef = Database.database().reference()
}
//single instance
static let shared = DataManager()
var databaseRef: DatabaseReference!
var userUID: String?
func createPost(post:PostModel, image:UIImage, progress:
@escaping (Double)->(),callback: @escaping (Bool) -> () ) {
//...
}
}
它使用单例模式,确保整个应用中只有一个实例与数据库进行交互。
4. Firebase 配置与使用
要使用 Firebase 的数据库和存储服务,需要进行以下操作:
-
安装库
:更新
Podfile
,添加以下模块:
pod 'Firebase/Database'
pod 'Firebase/Storage'
然后安装新库。
-
启用服务
:打开 Firebase 控制台,激活数据库和存储服务,使用实时数据库而非 Firestore。
-
更新数据库规则
:打开
RULES
标签,替换规则如下:
{
"rules": {
".read": "false",
".write": "false",
"myposts": {
".read": "false",
".write": "false",
"$uid": {
".read": "auth != null",
".write": "$uid === auth.uid"
}
},
"posts":{
".read": "auth != null",
".write": "auth != null"
}
}
}
这些规则限制了对数据库的未授权访问。
-
更新存储规则
:打开存储服务的
RULES
标签,替换规则如下:
service firebase.storage {
match /b/{bucket}/o {
match /posts/{userId}/{allPaths=**} {
allow read
allow write: if request.auth.uid == userId;
}
}
}
此规则限制用户只能在
/posts/<user-id>
位置写入数据。
5. 发布帖子的实现
createPost
函数用于发布帖子,具体步骤如下:
1.
生成唯一 ID
:
let key = databaseRef.child("posts").childByAutoId().key
let storageRef = Storage.storage().reference()
let photoPath = "posts/\(userID)/\(key)/photo.jpg"
let imageRef = storageRef.child(photoPath)
- 转换图像格式 :
let data = UIImageJPEGRepresentation(image, 0.9)
- 创建上传任务 :
let uploadTask = imageRef.putData(data!, metadata: metadata)
- 监听上传进度 :
uploadTask.observe(.progress) { snapshot in
let complete = 100.0 * Double(snapshot.progress!.completedUnitCount) / Double(snapshot.progress!.totalUnitCount)
progress(complete)
}
- 处理上传成功 :
uploadTask.observe(.success) { [unowned uploadTask, weak self] snapshot in
uploadTask.removeAllObservers()
post.photoURL = photoPath
post.width = Int(image.size.width)
post.height = Int(image.size.height)
var postData = post.toDict
let childUpdates = ["/posts/\(key)": postData,
"/myposts/\(userID)/\(key)/": postData]
self?.databaseRef.updateChildValues(childUpdates)
callback(true)
}
- 处理上传失败 :
uploadTask.observe(.failure) { [unowned uploadTask] snapshot in
uploadTask.removeAllObservers()
callback(false)
if let error = snapshot.error as NSError? {
switch (StorageErrorCode(rawValue: error.code)!) {
case .objectNotFound:
print("object not found")
break
case .unauthorized:
print("user has no permissions")
break
case .cancelled:
print("upload was cancelled")
break
case .unknown:
break
default:
break
}
}
}
6. 图像过滤功能
可以使用 CoreImage 对图像应用滤镜,以下是一个简单的函数:
func filter(_ image: UIImage, filter name:String) -> UIImage {
if name == "" {
return image
}
if let eaContext = EAGLContext(api: .openGLES2) {
let context = CIContext(eaglContext: eaContext)
let ciImage = CIImage(image: image)
if let filter = CIFilter(name: name) {
filter.setValue(ciImage, forKey: kCIInputImageKey)
if let outputImage = filter.outputImage,
let cgImg = context.createCGImage(outputImage, from: outputImage.extent) {
return UIImage(cgImage: cgImg, scale: image.scale, orientation: image.imageOrientation)
} else {
return UIImage()
}
}
}
return UIImage()
}
一些常见的滤镜名称如下表所示:
| 滤镜名称 | 效果描述 |
| ---- | ---- |
| CIPhotoEffectMono | 黑白照片效果 |
| CIPhotoEffectTonal | 色调照片效果 |
| CIPhotoEffectNoir | 黑白电影效果 |
| CIPhotoEffectFade | 褪色效果 |
| CIPhotoEffectChrome | 复古铬色效果 |
| CIPhotoEffectProcess | 冲印效果 |
| CIPhotoEffectTransfer | 转印效果 |
| CIPhotoEffectInstant | 即时成像效果 |
| CISepiaTone | 棕褐色调效果 |
| CIGaussianBlur | 高斯模糊效果 |
7. 主屏幕的实现
主屏幕将显示其他用户发布的所有近期帖子,实现步骤如下:
1.
设计 UI
:创建
HomeFeedViewController.swift
文件,添加
UICollectionView
属性,并将其与故事板中的视图控制器连接。确保
UICollectionView
铺满整个屏幕并到达安全区域。
class HomeFeedViewController: UIViewController {
private let reuseIdentifier = "FeedCell"
var model:[PostModel]?
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
model = []
}
}
-
创建单元格类
:通过
File | New File... (cmd + N)创建一个UICollectionViewCell子类FeedViewCell,并勾选Also create XIB file。在.xib文件中定义单元格的 UI。
class FeedViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
avatarImage.layer.cornerRadius = avatarImage.frame.height / 2
avatarImage.clipsToBounds = true
}
@IBOutlet weak var avatarImage: UIImageView!
@IBOutlet weak var avatarName: UILabel!
@IBOutlet weak var image: UIImageView!
@IBOutlet private weak var imageHeightConstraint: NSLayoutConstraint!
@IBOutlet private weak var imageWidthConstraint: NSLayoutConstraint!
var imageDimentions: CGSize = .zero {
didSet {
let imageWidth = UIScreen.main.bounds.width
let scaleRatio = imageDimentions.width/imageWidth
let scaledHeigth = imageDimentions.height/scaleRatio
imageWidthConstraint.constant = imageWidth
imageHeightConstraint.constant = scaledHeigth
}
}
}
-
加载数据
:在
DataManager类中实现fetchHomeFeed函数,用于从 Firebase 数据库加载所有帖子。
func fetchHomeFeed( callback: @escaping ([PostModel])->()) {
let ref = databaseRef.child("posts")
ref.observeSingleEvent(of: .value, with: { snapshot in
let items: [PostModel] = snapshot.children.compactMap { child in
guard let child = child as? DataSnapshot else {
return nil
}
return PostModel.init(snapshot: child)
}
DispatchQueue.main.async {
callback(items.reversed())
}
})
}
在
loadData
函数中调用该函数:
func loadData() {
model = []
DataManager.shared.fetchHomeFeed {[weak self] items in
if items.count > 0 {
self?.model? += items
self?.collectionView.reloadData()
}
}
}
以下是发布帖子的流程 mermaid 图:
graph LR
A[开始] --> B[生成唯一 ID]
B --> C[转换图像格式]
C --> D[创建上传任务]
D --> E{上传中}
E -->|监听进度| F(更新进度)
E -->|成功| G[保存帖子信息]
E -->|失败| H[处理错误]
G --> I[结束]
H --> I
通过以上步骤,我们可以构建一个功能较为完整的类 Instagram 应用,包括数据模型的构建、Firebase 后端的配置、帖子的发布、图像滤镜的应用以及主屏幕的显示等功能。
构建类 Instagram 应用全解析
8. 个人资料屏幕的实现
个人资料屏幕将展示用户的个人信息,并且允许用户设置用户名和上传头像。以下是实现个人资料屏幕的步骤:
1.
设计 UI
:创建一个新的视图控制器,命名为
ProfileViewController
。在这个视图控制器中,添加必要的 UI 元素,如用户名标签、头像图片视图、编辑按钮等。确保这些元素的布局合理,符合设计要求。
2.
连接数据
:在
ProfileViewController
中,获取用户的个人信息,如用户名和头像 URL。可以从 Firebase 数据库中读取这些信息,并将其显示在相应的 UI 元素上。
class ProfileViewController: UIViewController {
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var avatarImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
loadProfileData()
}
func loadProfileData() {
// 从 Firebase 数据库获取用户信息
if let userUID = DataManager.shared.userUID {
let userRef = DataManager.shared.databaseRef.child("users").child(userUID)
userRef.observeSingleEvent(of: .value, with: { snapshot in
if let userData = snapshot.value as? [String: Any] {
if let username = userData["username"] as? String {
self.usernameLabel.text = username
}
if let avatarURLString = userData["avatarURL"] as? String,
let avatarURL = URL(string: avatarURLString) {
// 使用第三方库(如 SDWebImage)加载头像
self.avatarImageView.sd_setImage(with: avatarURL, placeholderImage: UIImage(named: "placeholder"))
}
}
})
}
}
}
- 实现编辑功能 :为编辑按钮添加点击事件处理程序,当用户点击编辑按钮时,弹出一个编辑界面,允许用户修改用户名和上传新的头像。
@IBAction func editProfileButtonTapped(_ sender: UIButton) {
let alertController = UIAlertController(title: "编辑个人资料", message: nil, preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "用户名"
textField.text = self.usernameLabel.text
}
let saveAction = UIAlertAction(title: "保存", style: .default) { [weak self] _ in
if let newUsername = alertController.textFields?.first?.text {
if let userUID = DataManager.shared.userUID {
let userRef = DataManager.shared.databaseRef.child("users").child(userUID)
userRef.updateChildValues(["username": newUsername])
self?.usernameLabel.text = newUsername
}
}
}
let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: nil)
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
}
- 上传头像 :当用户选择上传新的头像时,使用 Firebase 存储服务将头像图片上传到服务器,并更新数据库中的头像 URL。
@IBAction func uploadAvatarButtonTapped(_ sender: UIButton) {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .photoLibrary
imagePicker.delegate = self
present(imagePicker, animated: true, completion: nil)
}
extension ProfileViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
if let userUID = DataManager.shared.userUID {
let storageRef = Storage.storage().reference().child("avatars").child("\(userUID).jpg")
if let imageData = selectedImage.jpegData(compressionQuality: 0.8) {
let uploadTask = storageRef.putData(imageData, metadata: nil) { metadata, error in
if let error = error {
print("头像上传失败: \(error.localizedDescription)")
} else {
storageRef.downloadURL { url, error in
if let downloadURL = url {
if let userUID = DataManager.shared.userUID {
let userRef = DataManager.shared.databaseRef.child("users").child(userUID)
userRef.updateChildValues(["avatarURL": downloadURL.absoluteString])
self.avatarImageView.image = selectedImage
}
}
}
}
}
}
}
}
picker.dismiss(animated: true, completion: nil)
}
}
9. 搜索屏幕的实现
搜索屏幕允许用户搜索其他用户或帖子。以下是实现搜索屏幕的步骤:
1.
设计 UI
:创建一个新的视图控制器,命名为
SearchViewController
。在这个视图控制器中,添加一个搜索框和一个结果列表视图。
2.
实现搜索功能
:当用户在搜索框中输入关键词时,根据关键词从 Firebase 数据库中搜索相关的用户或帖子,并将搜索结果显示在结果列表视图中。
class SearchViewController: UIViewController, UISearchBarDelegate {
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
var searchResults: [Any] = []
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
tableView.dataSource = self
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text, !searchText.isEmpty {
// 搜索用户
let usersRef = DataManager.shared.databaseRef.child("users")
usersRef.queryOrdered(byChild: "username").queryStarting(atValue: searchText).queryEnding(atValue: searchText + "\u{f8ff}").observeSingleEvent(of: .value, with: { snapshot in
var users: [UserModel] = []
for child in snapshot.children {
if let userSnapshot = child as? DataSnapshot,
let userData = userSnapshot.value as? [String: Any] {
let user = UserModel(snapshot: userSnapshot)
users.append(user)
}
}
self.searchResults = users
self.tableView.reloadData()
})
// 搜索帖子
let postsRef = DataManager.shared.databaseRef.child("posts")
postsRef.queryOrdered(byChild: "description").queryStarting(atValue: searchText).queryEnding(atValue: searchText + "\u{f8ff}").observeSingleEvent(of: .value, with: { snapshot in
var posts: [PostModel] = []
for child in snapshot.children {
if let postSnapshot = child as? DataSnapshot,
let postData = postSnapshot.value as? [String: Any] {
let post = PostModel(snapshot: postSnapshot)
posts.append(post)
}
}
self.searchResults += posts
self.tableView.reloadData()
})
}
}
}
extension SearchViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath)
let result = searchResults[indexPath.row]
if let user = result as? UserModel {
cell.textLabel?.text = user.username
} else if let post = result as? PostModel {
cell.textLabel?.text = post.description
}
return cell
}
}
10. 收藏屏幕的实现
收藏屏幕展示用户收藏的帖子。以下是实现收藏屏幕的步骤:
1.
设计 UI
:创建一个新的视图控制器,命名为
FavoritesViewController
。在这个视图控制器中,添加一个收藏列表视图。
2.
加载收藏数据
:从 Firebase 数据库中读取用户收藏的帖子,并将这些帖子显示在收藏列表视图中。
class FavoritesViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
var favoritePosts: [PostModel] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
loadFavoritePosts()
}
func loadFavoritePosts() {
if let userUID = DataManager.shared.userUID {
let favoritesRef = DataManager.shared.databaseRef.child("favorites").child(userUID)
favoritesRef.observeSingleEvent(of: .value, with: { snapshot in
var posts: [PostModel] = []
for child in snapshot.children {
if let postKey = child.key {
let postRef = DataManager.shared.databaseRef.child("posts").child(postKey)
postRef.observeSingleEvent(of: .value, with: { postSnapshot in
if let postData = postSnapshot.value as? [String: Any] {
let post = PostModel(snapshot: postSnapshot)
posts.append(post)
}
self.favoritePosts = posts
self.collectionView.reloadData()
})
}
}
})
}
}
}
extension FavoritesViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return favoritePosts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FavoriteCell", for: indexPath) as! FeedViewCell
let post = favoritePosts[indexPath.item]
// 设置单元格内容
cell.avatarName.text = post.author
// 加载图片
if let photoURL = post.photoURL {
let storageRef = Storage.storage().reference().child(photoURL)
storageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let data = data {
cell.image.image = UIImage(data: data)
}
}
}
return cell
}
}
以下是搜索功能的流程 mermaid 图:
graph LR
A[开始] --> B[用户输入关键词]
B --> C[搜索用户]
B --> D[搜索帖子]
C --> E[获取用户搜索结果]
D --> F[获取帖子搜索结果]
E --> G[合并结果]
F --> G
G --> H[显示结果列表]
H --> I[结束]
通过以上步骤,我们完成了类 Instagram 应用所有主要屏幕的实现,包括主屏幕、收藏屏幕、个人资料屏幕和搜索屏幕。这个应用现在可以从 Firebase 存储和数据库中获取内容,并展示给用户。同时,用户可以发布帖子、设置个人信息、搜索内容以及管理收藏。整个应用的功能更加完善,用户体验也得到了进一步提升。
超级会员免费看
548

被折叠的 条评论
为什么被折叠?



