构建类似 Instagram 的应用
1. Firebase 初始集成
首先,需要下载 GoogleService-Info.plist 文件,并将其添加到之前创建的标签式应用项目中。完成 Firebase 初始集成的最后一步是在 AppDelegate.swift 中添加以下代码:
import Firebase
FirebaseApp.configure()
代码需添加到以下函数中:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?)
-> Bool
如果一切正确,运行应用时,它将像以前一样启动,控制台会显示相应信息。
2. 实现认证屏幕
每个用户在启动类似 Instagram 的应用时,第一步是进行身份验证。
2.1 登录机制
用户使用移动应用时,需要向服务器和其他用户证明自己的身份。Firebase 会创建用户账户,每个用户至少需要一个账户才能使用该服务,不允许未经授权的访问。单个用户在 Firebase 中创建账户的数量没有限制,唯一要求是拥有有效的电子邮件或手机号码。将虚拟账户与现实世界中的唯一物品或服务关联起来,是减少系统中虚假账户数量的有效方法。我们将使用 Firebase 提供的默认账户创建服务。
Firebase 支持集成最流行的社交网络,如 Facebook 和 Twitter,但不支持 LinkedIn,不过有开源项目支持。此外,也可以像真正的 Instagram 一样使用手机号码登录。
2.2 创建登录屏幕
创建一个全新的视图控制器作为登录屏幕,目前只有使用电子邮件和密码登录这一种选项。如果想使用 Instagram 的标志或资源,需访问 https://en.instagram-brand.com/ 。
登录屏幕有一个标签和一个按钮,该按钮将启动由 Firebase 处理的登录过程。需要将“使用电子邮件登录”屏幕设置为应用的主要入口点,只需将指向标签式视图控制器的箭头指向新的视图控制器。别忘了给新视图控制器设置故事板 ID,例如 SignInViewController ,并给主标签视图控制器命名为 TabbarViewController 。
在 Podfile 中添加以下依赖:
pod 'FirebaseUI/Auth', '~> 4.0'
安装依赖并重新打开工作区。创建 SignInViewController.swift 文件,在其中创建 SignInViewController 类,代码如下:
import UIKit
import FirebaseAuthUI
class SignInViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func signInWithEmail(_ sender: Any) {
let authUI = FUIAuth.defaultAuthUI()
if let authViewController = authUI?.authViewController() {
present(authViewController, animated: true, completion: nil)
}
}
}
将该类与主故事板中的视图控制器关联,并为“使用电子邮件登录”按钮添加动作。此动作使用默认的 Firebase 流程处理用户登录过程。如果用户已有账户,会提示输入密码;否则,将创建与用户电子邮件关联的新账户。
在 AppDelegate 中添加额外逻辑,根据用户认证状态切换启动屏幕。如果用户已认证,应显示应用的主屏幕;否则,显示 SignInViewController :
class AppDelegate: UIResponder, UIApplicationDelegate, FUIAuthDelegate {
//....
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
let nc = NotificationCenter.default
nc.addObserver(forName: Notification.Name(
rawValue: "userSignedOut"),
object: nil, queue: nil) { [weak self]
notification in
//TODO: remove the stored user information
self?.openSingInScreen()
}
// handle the successful sign in
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self
let user = Auth.auth().currentUser
if let user = user {
save(user: user)
self.openMainViewController()
}
return true
}
//MARK:- helper functions
func save(user: User) {
//TODO: save the user in memory
}
func openSingInScreen() {
if let signInViewController = self.window?
.rootViewController?.storyboard?.instantiateViewController(
withIdentifier: "SignInViewController") as?
SignInViewController {
signInViewController.view.frame = (
self.window?.rootViewController?.view.frame)!
signInViewController.view.layoutIfNeeded()
UIView.transition(with: window!, duration: 0.3,
options: .transitionCrossDissolve, animations: {
self.window?.rootViewController = signInViewController
}, completion: { completed in
// nothing to do here
})
}
}
func openMainViewController() {
if let rootViewController = self.window?
.rootViewController?.storyboard?.instantiateViewController(
withIdentifier: "TabbarViewController") {
rootViewController.view.frame = (self.window?
.rootViewController?.view.frame)!
rootViewController.view.layoutIfNeeded()
//nice transition between views
UIView.transition(with: window!, duration: 0.3,
options: .transitionCrossDissolve, animations: {
self.window?.rootViewController = rootViewController
}, completion: { completed in
// maybe do something here
})
}
}
//MARK:- FUIAuthDelegate
func authUI(_ authUI: FUIAuth, didSignInWith user: User?,
error: Error?) {
// handle user and error as necessary
if let user = user {
save(user: user)
self.openMainViewController()
}
}
}
上述代码会在 Firebase 报告用户成功认证时触发。如果用户登录过一次,后续每次启动应用时都会自动进行认证。
在检查实现之前,需要在 Firebase 控制台中激活电子邮件/密码登录选项。具体操作是在 Firebase 控制台中选择创建的项目,打开“Authentication”屏幕,点击“Sign-in method”,编辑第一个选项并将其设置为“Enabled”。
3. 应用整体设计
在深入细节之前,我们要对应用的整体体验进行设计,使用包含五个不同部分的标签栏,各部分功能如下:
| 屏幕名称 | 功能描述 |
| ---- | ---- |
| 主页屏幕 | 包含照片的主要信息流,显示你关注的用户的所有新照片 |
| 搜索屏幕 | 用户可以在此搜索不同的内容 |
| 创建帖子屏幕 | 用户可以拍照并应用不同的滤镜,完成后照片将发布,所有用户都能在主页屏幕上看到 |
| 收藏屏幕 | 显示收藏的帖子 |
| 用户个人资料屏幕 | 显示你自己的所有帖子 |
打开故事板,创建三个新的空视图控制器(之前步骤应该已经生成了两个)。在每个视图控制器的中心添加一个标签,并给它一个合适的描述性名称。将这些视图控制器连接到主标签栏,别忘了添加图标,这会让应用更出众。
4. 自定义标签栏按钮
默认状态栏上的按钮可以通过多种方式进行自定义,例如设置自定义图标、更改每个图标下方的文本、添加动画效果,也可以更改颜色。我们希望吸引用户对特定按钮(即触发创建帖子流程的相机按钮)的注意。
为实现这一目标,使用外部开源库 ESTabBarController-swift 。在 Podfile 中添加以下依赖:
pod 'ESTabBarController-swift'
然后更改故事板中与标签栏视图控制器关联的类,创建一个继承自 ESTabbarController 的自定义类 InstagramTabbarController ,代码如下:
import UIKit
import ESTabBarController_swift
class InstagramTabbarController: ESTabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.shouldHijackHandler = {
tabbarController, viewController, index in
if index == 2 {
return true
}
return false
}
self.didHijackHandler = {
[weak self] tabbarController, viewController, index in
DispatchQueue.main.async {
self?.pesentPicker()
}
}
//update the middle icon
if let viewController = self.viewControllers?[2] {
viewController.tabBarItem = ESTabBarItem.init(
AnimatedContentView(), title: nil, image:
UIImage(named: "create_post"), selectedImage:
UIImage(named: "create_post"))
}
}
func pesentPicker() {
//...
}
}
这个类重新定义了第三个图标(索引为 2)的行为。默认行为是显示与标签栏视图控制器关联的视图控制器,而我们需要显示一个图像选择器。如果用户没有选择用于帖子的图像,应立即返回原来的位置。
在 shouldHijackHandler 中,为需要自定义的索引返回 true (这里只有索引 2)。实际按下图标时触发的代码放在 didHijackHandler 中。
使用自定义的 AnimatedContentView 类来实现第三个图标的新外观:
class AnimatedContentView: ESTabBarItemContentView {
override init(frame: CGRect) {
super.init(frame: frame)
iconColor = .black
highlightIconColor = .white
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func highlightAnimation(animated: Bool, completion: (()
-> ())?) {
UIView.beginAnimations("small", context: nil)
UIView.setAnimationDuration(0.2)
let transform = self.imageView.transform.scaledBy(x: 0.8, y: 0.8)
self.imageView.transform = transform
UIView.commitAnimations()
completion?()
}
public override func dehighlightAnimation(animated: Bool,
completion: (() -> ())?) {
UIView.beginAnimations("big", context: nil)
UIView.setAnimationDuration(0.2)
let transform = CGAffineTransform.identity
self.imageView.transform = transform
UIView.commitAnimations()
completion?()
}
}
highlightAnimation 函数会缩小整个图像,营造按钮被按下的错觉;另一个函数则恢复其初始大小和位置。通过调整这些函数中的数值,可以改变当前的行为。
5. 创建帖子流程
我们将使用外部库 YPImagePicker 来处理渲染用户媒体、选择图像和应用滤镜的复杂过程。这是一个用 Swift 实现类似 Instagram 照片和视频选择器的开源库。在 Podfile 中添加以下依赖:
pod 'YPImagePicker'
然后像之前一样安装新的依赖。
在 InstagramTabbarController 中,当检测到中间标签栏按钮被点击时,调用 pesentPicker 函数:
func pesentPicker() {
var config = YPImagePickerConfiguration()
config.onlySquareImagesFromLibrary = false
config.onlySquareImagesFromCamera = true
config.libraryTargetImageSize = .original
config.usesFrontCamera = true
config.showsFilters = true
config.shouldSaveNewPicturesToAlbum = !true
config.albumName = "IstagramLikeApp"
config.startOnScreen = .library
let picker = YPImagePicker(configuration: config)
picker.didSelectImage = {
img in
//TODO: ...
}
present(picker, animated: true, completion: nil)
}
如果直接运行应用,它会崩溃,因为 iOS 需要向用户请求访问照片的权限。需要在 Info.plist 文件中定义特殊键,并关联简短的描述文本,让用户知道应用为何需要访问照片库、设备相机和麦克风。具体操作是在 Info.plist 文件中添加以下内容:
<key>NSCameraUsageDescription</key>
<string>InstagramLike app needs access to your camera.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>InstagramLike app needs access to your photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>InstagramLike app needs access to your microphone.</string>
添加描述后,应用应该可以正常运行,但选择图像并应用滤镜后,无法离开选择器。问题出在 picker.didSelectImage 闭包中,需要在选择图像时手动关闭屏幕。
创建一个新的屏幕来处理添加标题和实际分享帖子的操作。首先在故事板中创建一个新的视图控制器,添加一个宽度和高度均为 110 点的 UIImageView ,旁边放置一个 TextView ,让用户可以为图像添加简短描述。
创建 CreatePostViewController.swift 文件,创建 CreatePostViewController 类并与新视图控制器关联:
class CreatePostViewController: UIViewController {
override var prefersStatusBarHidden: Bool { return true }
private let placeholderText = "Write a caption..."
public var image:UIImage?
@IBOutlet weak var photo: UIImageView!
@IBOutlet weak var textView: UITextView! {
didSet {
textView.textColor = .gray
textView.text = placeholderText
textView.selectedRange = NSRange()
}
}
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
photo.image = image
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Share", style: .done, target: self,
action: #selector(createPost))
}
@objc func createPost() {
self.dismiss(animated: true, completion: nil)
}
}
该视图控制器在右上角添加了一个“分享”按钮,点击该按钮应创建新帖子并关闭选择器,让用户回到之前的操作。实际创建帖子并将其保存到 Firebase 服务器的代码将在后续添加。
为了给 TextView 添加占位符文本,在 CreatePostViewController 类中添加以下扩展:
extension CreatePostViewController: UITextViewDelegate {
func textViewDidChangeSelection(_ textView: UITextView) {
// Move cursor to beginning on first tap
if textView.text == placeholderText {
textView.selectedRange = NSRange()
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range:
NSRange, replacementText text: String) -> Bool {
if textView.text == placeholderText && !text.isEmpty {
textView.text = nil
textView.textColor = .black
textView.selectedRange = NSRange()
}
return true
}
func textViewDidChange(_ textView: UITextView) {
if textView.text.isEmpty {
textView.textColor = .gray
textView.text = placeholderText
}
}
}
TextView 根据用户输入的内容决定显示什么文本。如果文本为空,显示占位符文本;如果用户输入了任何文本,则显示该文本,并相应地管理选择。
最后,在 InstagramTabbarController 的 didSelectImage 闭包中将新的 CreatePostViewController 与选择器关联起来:
picker.didSelectImage = {
[unowned picker, weak self] img in
if let viewController = self?.storyboard?
.instantiateViewController(withIdentifier: "CreatePost")
as? CreatePostViewController {
viewController.image = img
// Use Fade transition instead of default push animation
let transition = CATransition()
transition.duration = 0.3
通过以上步骤,我们逐步构建了一个类似 Instagram 的应用,包括 Firebase 集成、用户认证、应用整体设计以及自定义标签栏和创建帖子的功能。后续还可以进一步完善和扩展应用的功能。
构建类似 Instagram 的应用(下半部分)
6. 连接创建帖子视图控制器
在 InstagramTabbarController 的 didSelectImage 闭包中,我们已经开始将 CreatePostViewController 与选择器关联起来。接下来,我们要完成这个关联过程,确保用户选择图片后能顺利进入创建帖子的界面。
picker.didSelectImage = {
[unowned picker, weak self] img in
if let viewController = self?.storyboard?
.instantiateViewController(withIdentifier: "CreatePost")
as? CreatePostViewController {
viewController.image = img
// Use Fade transition instead of default push animation
let transition = CATransition()
transition.duration = 0.3
transition.type = .fade
self?.view.window?.layer.add(transition, forKey: kCATransition)
self?.present(viewController, animated: false, completion: nil)
}
picker.dismiss(animated: true, completion: nil)
}
在上述代码中,我们完成了以下操作:
1. 实例化 CreatePostViewController 并将选择的图片赋值给它。
2. 使用 CATransition 创建一个淡入淡出的过渡效果。
3. 显示 CreatePostViewController 。
4. 关闭图片选择器。
7. 保存帖子到 Firebase
目前, CreatePostViewController 中的 createPost 方法只是简单地关闭了视图控制器,我们需要实现将帖子保存到 Firebase 的功能。
首先,确保已经在项目中导入了 Firebase 的存储和数据库模块:
import FirebaseStorage
import FirebaseDatabase
然后,修改 CreatePostViewController 中的 createPost 方法:
@objc func createPost() {
guard let image = image, let caption = textView.text, !caption.isEmpty else {
return
}
// 上传图片到 Firebase Storage
let storageRef = Storage.storage().reference().child("posts/\(UUID().uuidString).jpg")
if let uploadData = image.jpegData(compressionQuality: 0.8) {
storageRef.putData(uploadData, metadata: nil) { (metadata, error) in
if let error = error {
print("Error uploading image: \(error)")
return
}
// 获取图片的下载 URL
storageRef.downloadURL { (url, error) in
if let error = error {
print("Error getting download URL: \(error)")
return
}
guard let imageURL = url?.absoluteString else {
return
}
// 保存帖子信息到 Firebase Database
let postData = [
"caption": caption,
"imageURL": imageURL,
"timestamp": ServerValue.timestamp()
] as [String : Any]
let databaseRef = Database.database().reference().child("posts")
let newPostRef = databaseRef.childByAutoId()
newPostRef.setValue(postData) { (error, ref) in
if let error = error {
print("Error saving post: \(error)")
} else {
self.dismiss(animated: true, completion: nil)
}
}
}
}
}
}
上述代码的执行流程如下:
1. 检查图片和标题是否存在。
2. 生成一个唯一的文件名,将图片上传到 Firebase Storage。
3. 获取图片的下载 URL。
4. 将帖子信息(标题、图片 URL 和时间戳)保存到 Firebase Database。
5. 关闭 CreatePostViewController 。
8. 显示帖子列表
在主页屏幕上,我们需要显示所有用户的帖子列表。创建一个新的视图控制器 HomeViewController ,并在故事板中关联它。
首先,在 HomeViewController 中导入必要的模块:
import UIKit
import FirebaseDatabase
import SDWebImage
然后,实现 UITableViewDataSource 和 UITableViewDelegate 协议:
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
// 从 Firebase Database 获取帖子数据
let databaseRef = Database.database().reference().child("posts")
databaseRef.observe(.value) { (snapshot) in
self.posts.removeAll()
for child in snapshot.children {
if let postSnapshot = child as? DataSnapshot,
let postData = postSnapshot.value as? [String: Any],
let caption = postData["caption"] as? String,
let imageURL = postData["imageURL"] as? String,
let timestamp = postData["timestamp"] as? TimeInterval {
let post = Post(caption: caption, imageURL: imageURL, timestamp: timestamp)
self.posts.append(post)
}
}
self.posts.sort { $0.timestamp > $1.timestamp }
self.tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostTableViewCell
let post = posts[indexPath.row]
cell.captionLabel.text = post.caption
cell.timestampLabel.text = Date(timeIntervalSince1970: post.timestamp).timeAgoDisplay()
cell.postImageView.sd_setImage(with: URL(string: post.imageURL), placeholderImage: UIImage(named: "placeholder"))
return cell
}
}
struct Post {
let caption: String
let imageURL: String
let timestamp: TimeInterval
}
extension Date {
func timeAgoDisplay() -> String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: self, relativeTo: Date())
}
}
上述代码的主要功能如下:
1. 从 Firebase Database 获取所有帖子数据。
2. 将帖子数据存储在 posts 数组中,并按时间戳排序。
3. 使用 UITableView 显示帖子列表。
4. 在单元格中显示帖子的标题、图片和时间戳。
9. 总结
通过以上步骤,我们完成了一个类似 Instagram 的应用的主要功能开发,包括:
1. Firebase 集成和用户认证。
2. 自定义标签栏和创建帖子功能。
3. 图片选择和滤镜应用。
4. 帖子保存到 Firebase Storage 和 Database。
5. 主页屏幕显示帖子列表。
以下是整个应用开发流程的 mermaid 流程图:
graph LR
A[Firebase 初始集成] --> B[实现认证屏幕]
B --> C[应用整体设计]
C --> D[自定义标签栏按钮]
D --> E[创建帖子流程]
E --> F[连接创建帖子视图控制器]
F --> G[保存帖子到 Firebase]
G --> H[显示帖子列表]
这个应用还有很多可以扩展和优化的地方,例如添加用户个人资料页面、点赞和评论功能、推送通知等。希望这篇文章能帮助你构建出一个功能丰富的类似 Instagram 的应用。
超级会员免费看
34

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



