DB와 연결하기 #2
프로필 사진 표시하기
프로필 사진 표시하기
| Kingfisher import 하기
Kingfisher
URL을 사용해 네트워크에서 쉽게 이미지를 다운로드하여 사용할 수 있는 Package다.
Swift를 사용해 만들어 졌기에 간단하게 사용할 수 있다.
페이지의 링크를 사용해 Swift Package Manager로 추가해 주면 된다.
프로필 사진 표시하기
| ContentView > toolbar
import SwiftUI
import Kingfisher
struct ContentView: View {
@State private var showMenu = false
@EnvironmentObject var viewModel: AuthViewModel
var body: some View {
kingfisher를 설치했다면 사용할 코드에서 import해 사용하면 된다.
Kingfisher로 import 했다.
extension ContentView {
var mainInterfaceView: some View {
ZStack(alignment: .topLeading) {
.
.
.
}
.navigationTitle("Home")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation(.easeInOut) {
showMenu.toggle()
}
} label: {
Circle()
.frame(width: 32, height: 32)
}
}
}
.onAppear {
showMenu = false
}
}
}
Tweet들을 나열해 보여주는 FeedView는 좌측 상단에 프로필 이미지를 표시하고,
터치 시 현재 로그인된 계정의 정보와 프로필 사진을 보여주는 SideMenu를 표시한다.
지금은 표시할 이미지가 없어 파란색 원에 불과하지만 현재 접속 중인 계정의 프로필 사진을 표시하도록 구현한다.
.toolbar{
ToolbarItem(placement: .navigationBarLeading) {
if let user = viewModel.currentUser {
}
}
}
현재 접속정보를 얻기 위해 if-let 문을 사용해 currentUser 속성을 확인한다.
.toolbar{
ToolbarItem(placement: .navigationBarLeading) {
if let user = viewModel.currentUser {
Button {
withAnimation(.easeInOut) {
showMenu.toggle()
}
} label: {
}
}
}
}
기능 자체는 기존의 Button과 동일하게 SideMenue를 toggle 하도록 구현한다.
.toolbar{
ToolbarItem(placement: .navigationBarLeading) {
if let user = viewModel.currentUser {
Button {
withAnimation(.easeInOut) {
showMenu.toggle()
}
} label: {
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 32, height: 32)
}
}
}
}
KFImage 메서드의 parameter에 현재 계정의 profileImageUrl을 전달하기만 하면 간단히 이미지를 불러온다.
디자인에 맞게 이미지를 변형할 수 있도록 resizable 등의 modifier를 적용하면 된다.
Source
import SwiftUI
import Kingfisher
struct ContentView: View {
@State private var showMenu = false
@EnvironmentObject var viewModel: AuthViewModel
var body: some View {
Group {
//need login
if viewModel.userSession == nil {
LoginView()
} else {
//login successfuly
mainInterfaceView
}
}
}
}
extension ContentView {
var mainInterfaceView: some View {
ZStack(alignment: .topLeading) {
MainTabView()
.toolbar(showMenu ? .hidden : .visible)
if showMenu {
ZStack {
withAnimation(.easeInOut) {
Color(.black)
.opacity(showMenu ? 0.25 : 0.0)
}
}.onTapGesture {
withAnimation(.easeInOut) {
showMenu = false
}
}
.ignoresSafeArea()
}
SideMenuView()
.frame(width: 300)
.offset(x: showMenu ? 0 : -300, y: 0)
}
.navigationTitle("Home")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItem(placement: .navigationBarLeading) {
if let user = viewModel.currentUser {
Button {
withAnimation(.easeInOut) {
showMenu.toggle()
}
} label: {
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 32, height: 32)
}
}
}
}
.onAppear {
showMenu = false
}
}
}
프로필 사진 표시하기
| ProfileView
import SwiftUI
import Kingfisher
struct ProfileView: View {
@State private var selectedFilter: TweetFilterViewModel = .tweets
@Environment(\.dismiss) var dismiss
@Namespace var animation
마찬가지로 Kingfisher를 import 한다.
struct ProfileView: View {
@State private var selectedFilter: TweetFilterViewModel = .tweets
@Environment(\.dismiss) var dismiss
@Namespace var animation
private let user: User
init(user: User) {
self.user = user
}
ProfileView는 대상 사용자의 정보를 표시한다.
따라서 파라미터를 하나 만들어 User 타입의 데이터를 받아 올 수 있도록 구현한다.
전달된 User 데이터는 ProfileView가 초기화 되는 시점에 user로 바인딩된다.
var headerView: some View {
ZStack(alignment: .bottomLeading) {
Color(.systemBlue)
.ignoresSafeArea()
VStack {
Button {
dismiss()
} label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 16)
.foregroundColor(.white)
}
.offset(x: 16, y: 12)
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 72, height: 72)
.offset(x: 16, y: 24)
}
}
.frame(height: 96)
.toolbar(.hidden, for: .navigationBar)
}
전달된 user 데이터의 profileImageUrl 속성을 사용해 KFImage 메서드를 호출해 이미지를 받아 온다.
디자인에 맞춰 resizable 등의 modifier를 적용한다.
var userInfoDetails: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(user.fullname)
.font(.title2).bold()
Image(systemName: "checkmark.seal.fill")
.foregroundColor(Color(.systemBlue))
}
Text("@\(user.username)")
.font(.subheadline)
.foregroundColor(.gray)
Text("Eureka!")
.font(.subheadline)
.padding(.vertical)
HStack(spacing: 24) {
HStack {
Image(systemName: "mappin.and.ellipse")
Text("Syracuse, Italia")
}
HStack {
Image(systemName: "link")
Text("https://chillog.page")
}
}
.font(.caption)
.foregroundColor(.gray)
UserStatsView()
.padding(.vertical
)
}
.padding(.horizontal)
}
UserInfoDetails에서는 전달된 user의 계정과 이름을 표시한다.
각각 username과 fullname 속성을 전달한다.
Source
import SwiftUI
import Kingfisher
struct ProfileView: View {
@State private var selectedFilter: TweetFilterViewModel = .tweets
@Environment(\.dismiss) var dismiss
@Namespace var animation
private let user: User
init(user: User) {
self.user = user
}
var body: some View {
VStack(alignment: .leading) {
headerView
actionButtons
userInfoDetails
tweetFilterBar
tweetsView
Spacer()
}
}
}
extension ProfileView {
var headerView: some View {
ZStack(alignment: .bottomLeading) {
Color(.systemBlue)
.ignoresSafeArea()
VStack {
Button {
dismiss()
} label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 16)
.foregroundColor(.white)
}
.offset(x: 16, y: 12)
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 72, height: 72)
.offset(x: 16, y: 24)
}
}
.frame(height: 96)
.toolbar(.hidden, for: .navigationBar)
}
var actionButtons: some View {
HStack(spacing: 12) {
Spacer()
Image(systemName: "bell.badge")
.font(.title3)
.padding(6)
.overlay {
Circle()
.stroke(Color.gray, lineWidth: 0.75)
}
Button {
} label: {
Text("Edit Profile")
.font(.subheadline).bold()
.frame(width: 120, height: 32)
.overlay {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.gray, lineWidth: 0.75)
}
}
}
.padding(.trailing)
}
var userInfoDetails: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(user.fullname)
.font(.title2).bold()
Image(systemName: "checkmark.seal.fill")
.foregroundColor(Color(.systemBlue))
}
Text("@\(user.username)")
.font(.subheadline)
.foregroundColor(.gray)
Text("Eureka!")
.font(.subheadline)
.padding(.vertical)
HStack(spacing: 24) {
HStack {
Image(systemName: "mappin.and.ellipse")
Text("Syracuse, Italia")
}
HStack {
Image(systemName: "link")
Text("https://chillog.page")
}
}
.font(.caption)
.foregroundColor(.gray)
UserStatsView()
.padding(.vertical
)
}
.padding(.horizontal)
}
var tweetFilterBar: some View {
HStack {
ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.subheadline)
.fontWeight(selectedFilter == item ? .semibold : .regular)
.foregroundColor(selectedFilter == item ? .black : .gray)
if selectedFilter == item {
Capsule()
.foregroundColor(Color(.systemBlue))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
} else {
Capsule()
.foregroundColor(Color(.clear))
.frame(height: 3)
}
}
.onTapGesture {
withAnimation(.easeOut) {
self.selectedFilter = item
}
}
}
}
.overlay {
Divider().offset(x: 0, y: 16)
}
}
var tweetsView: some View {
ScrollView {
LazyVStack {
ForEach(0 ... 9, id: \.self) { _ in
TweetRowView()
.padding()
}
}
}
}
}
프로필 사진 표시하기
| SideMenu
struct SideMenuView: View {
@EnvironmentObject var authViewModel: AuthViewModel
var body: some View {
if let user = authViewModel.currentUser {
VStack(alignment: .leading, spacing: 32) {
VStack(alignment: .leading) {
Circle()
.frame(width: 48, height: 48)
VStack(alignment: .leading, spacing: 4) {
Text(user.fullname)
.font(.headline)
Text("@\(user.username)")
.font(.caption)
.foregroundColor(.gray)
}
UserStatsView()
.padding(.vertical)
}
.padding(.leading)
ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in
if viewModel == .profile {
NavigationLink(destination: ProfileView()) {
SideMenuRowView(viewModel: viewModel)
}
} else if viewModel == .logout {
Button {
authViewModel.signOut()
} label: {
SideMenuRowView(viewModel: viewModel)
}
} else {
SideMenuRowView(viewModel: viewModel)
}
}
Spacer()
}
.background(Color.white)
}
}
}
기존까지는 사진을 제외한 계정의 이름과 사용자 명만 표시했다.
위에서 Menu 버튼을 변경한 것과 같은 방법으로 이미지를 대체하면 된다.
import SwiftUI
import Kingfisher
struct SideMenuView: View {
@EnvironmentObject var authViewModel: AuthViewModel
var body: some View {
if let user = authViewModel.currentUser {
Kingfisher를 import 한다.
var body: some View {
if let user = authViewModel.currentUser {
VStack(alignment: .leading, spacing: 32) {
VStack(alignment: .leading) {
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 48, height: 48)
VStack(alignment: .leading, spacing: 4) {
Text(user.fullname)
.font(.headline)
Text("@\(user.username)"
currentUser의 profileImageUrl을 KFImage에 전달해 이미지를 받아온다.
디자인할 수 있도록 resizable을 포함한 modifier를 적용한다.
ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in
if viewModel == .profile {
NavigationLink(destination: ProfileView(user: user)) {
SideMenuRowView(viewModel: viewModel)
}
} else if viewModel == .logout {
Button {
authViewModel.signOut()
} label: {
SideMenuRowView(viewModel: viewModel)
}
SideMenu의 Profile 메뉴는 현재 사용자의 프로필을 표시한다.
NavigationLink로 ProfileView를 연결하고 파라미터로 현재 접속 중인 계정을 전달한다.
Source
import SwiftUI
import Kingfisher
struct SideMenuView: View {
@EnvironmentObject var authViewModel: AuthViewModel
var body: some View {
if let user = authViewModel.currentUser {
VStack(alignment: .leading, spacing: 32) {
VStack(alignment: .leading) {
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 48, height: 48)
VStack(alignment: .leading, spacing: 4) {
Text(user.fullname)
.font(.headline)
Text("@\(user.username)")
.font(.caption)
.foregroundColor(.gray)
}
UserStatsView()
.padding(.vertical)
}
.padding(.leading)
ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in
if viewModel == .profile {
NavigationLink(destination: ProfileView(user: user)) {
SideMenuRowView(viewModel: viewModel)
}
} else if viewModel == .logout {
Button {
authViewModel.signOut()
} label: {
SideMenuRowView(viewModel: viewModel)
}
} else {
SideMenuRowView(viewModel: viewModel)
}
}
Spacer()
}
.background(Color.white)
}
}
}
프로필 사진 표시하기
| ExploreView
struct ExploreView: View {
var body: some View {
NavigationStack {
VStack {
ScrollView {
LazyVStack {
ForEach(0 ... 25, id: \.self) { _ in
NavigationLink {
// ProfileView(user: <#T##User#>)
} label: {
UserRowView()
}
}
}
}
}
.navigationTitle("Explore")
.navigationBarTitleDisplayMode(.inline)
}
}
}
지금은 전달할 사용자가 존재하지 않는다.
오류를 무시하고 구현 사항을 확인할 수 있도록 ProfileView를 표시하는 부분을 주석처리 하거나,
임시 User 객체를 선언해 전달할 수 있도록 한다.
이번엔 주석처리로 진행했다.
'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글
15. 기능구현 #4 (0) | 2023.01.11 |
---|---|
14. 버그 수정 #1 (0) | 2023.01.11 |
12. DB와 연결하기 #1 (0) | 2023.01.05 |
11. 기능 구현하기 #3 (0) | 2022.12.22 |
10. 기능 구현하기 #2 (0) | 2022.12.20 |