Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>messenger.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
12 changes: 8 additions & 4 deletions messenger/messenger/ChatController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
import SwiftUI
import Combine

// ChatController needs to be a BindableObject in order
// ChatController needs to be a ObservableObject in order
// to be accessible by SwiftUI
class ChatController: BindableObject {

class ChatController : ObservableObject {
// didChange will let the SwiftUI know that some changes have happened in this object
// and we need to rebuild all the views related to that object
var didChange = PassthroughSubject<Void, Never>()

var messages = [
// We've relocated the messages from the main SwiftUI View. Now, if you wish, you can handle the networking part here and populate this array with any data from your database. If you do so, please share your code and let's build the first global open-source chat app in SwiftUI together
@Published var messages = [
ChatMessage(message: "Oh hey there!", avatar: "A", color: .red),
ChatMessage(message: "lol Great to hear from you!", avatar: "B", color: .blue)
]
Expand All @@ -27,5 +31,5 @@ class ChatController: BindableObject {
// here we let the SwiftUI know that we need to rebuild the views
didChange.send(())
}

}

189 changes: 145 additions & 44 deletions messenger/messenger/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
//
// ContentView.swift
// messenger
//
// Created by Scott Clampet on 7/8/19.
// Copyright © 2019 sc. All rights reserved.
//

//
// ChatMessage.swift
// messenger
Expand All @@ -15,76 +7,186 @@
//

import SwiftUI
import Combine

// let's create a structure that will represent each message in chat
struct ChatMessage : Hashable {
var message: String
var avatar: String
var color: Color
// is Me will be true if We sent the message
var isMe: Bool = false
}

struct ContentView : View {
// Necessary for moving the textField when keyboard appears
@ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 1)
@State private var name = Array<String>.init(repeating: "", count: 1)

// @State here is necessary to make the composedMessage variable accessible from different views
@State var composedMessage: String = ""
@EnvironmentObject var chatController: ChatController

var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack {
Text("Chat Room").font(.largeTitle).bold()
Spacer()
}.padding()

// the VStack is a vertical stack where we place all our substacks like the List and the TextField
VStack {
// I've removed the text line from here and replaced it with a list
// List is the way you should create any list in SwiftUI
List {
ForEach(chatController.messages.identified(by: \.self)) {
ChatRow(chatMessage: $0)
// we have several messages so we use the For Loop
ForEach(chatController.messages, id: \.self) { msg in
ChatRow(chatMessage: msg)
}
}

// TextField are aligned with the Send Button in the same line so we put them in HStack
HStack {
Group {
TextField($composedMessage, placeholder: Text("Message...")).frame(minHeight: 50).padding(.leading)
}.border(Color.gray, width: 3, cornerRadius: 25).padding()
// this textField generates the value for the composedMessage @State var
TextField("Message...", text: $composedMessage).frame(minHeight: CGFloat(30))
.foregroundColor(.black)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(GeometryGetter(rect : $kGuardian.rects[0]))
// the button triggers the sendMessage() function written in the end of current View
Button(action: sendMessage) {
Text("Send").font(.title)
}
Text("Send")
}
.padding(.trailing)
.frame(minHeight: 90)

Rectangle().frame(height: 35)

}.edgesIgnoringSafeArea(.bottom)
}
.frame(minHeight: CGFloat(50)).padding()
.offset(y: kGuardian.slide)
.animation(.easeInOut(duration: 1.0))
// that's the height of the HStack
}.onAppear {
self.kGuardian.addObserver()
}.onDisappear{
self.kGuardian.removeObserver()
}

}

func sendMessage() {
chatController.sendMessage(ChatMessage(message: composedMessage, avatar: "C", color: .green, isMe: true))
composedMessage = ""
}
}
// Following code is needed for moving textField.
// You can find it here:
/*https://stackoverflow.com/questions/56491881/move-textfield-up-when-thekeyboard-has-appeared-by-using-swiftui-ios */
struct GeometryGetter: View {
@Binding var rect: CGRect

var body: some View {
GeometryReader { geometry in
Group { () -> AnyView in
DispatchQueue.main.async {
self.rect = geometry.frame(in: .global)
}

return AnyView(Color.clear)
}
}
}
}
final class KeyboardGuardian: ObservableObject {
public var rects: Array<CGRect>
public var keyboardRect: CGRect = CGRect()

// keyboardWillShow notification may be posted repeatedly,
// this flag makes sure we only act once per keyboard appearance
public var keyboardIsHidden = true

@Published var slide: CGFloat = 0

var showField: Int = 0 {
didSet {
updateSlide()
}
}
init(textFieldCount: Int) {
self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount)
}

func addObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
}

func removeObserver() {
NotificationCenter.default.removeObserver(self)
}

deinit {
NotificationCenter.default.removeObserver(self)
}



@objc func keyBoardWillShow(notification: Notification) {
if keyboardIsHidden {
keyboardIsHidden = false
if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
keyboardRect = rect
updateSlide()
}
}
}

@objc func keyBoardDidHide(notification: Notification) {
keyboardIsHidden = true
updateSlide()
}

func updateSlide() {
if keyboardIsHidden {
slide = 0
} else {
let tfRect = self.rects[self.showField]
let diff = keyboardRect.minY - tfRect.maxY

if diff > 0 {
slide += diff
} else {
slide += min(diff, 0)
}

}
}
}



// ChatRow will be a view similar to a Cell in standard Swift
struct ChatRow : View {
// we will need to access and represent the chatMessages here
var chatMessage: ChatMessage

// body - is the body of the view, just like the body of the first view we created when opened the project
var body: some View {
// HStack - is a horizontal stack. We let the SwiftUI know that we need to place
// all the following contents horizontally one after another
Group {
if !chatMessage.isMe {
HStack {
Text(chatMessage.avatar)
Text(chatMessage.message)
.bold()
.foregroundColor(Color.white)
.padding(10)
.background(chatMessage.color, cornerRadius: 10)
Spacer()
Group {
Text(chatMessage.avatar)
Text(chatMessage.message)
.bold()
.padding(10)
.foregroundColor(Color.white)
.background(chatMessage.color)
.cornerRadius(10)
}
}
} else {
HStack {
Spacer()
Text(chatMessage.message)
.bold()
.foregroundColor(Color.white)
.padding(10)
.background(chatMessage.color, cornerRadius: 10)
Text(chatMessage.avatar)
Group {
Spacer()
Text(chatMessage.message)
.bold()
.foregroundColor(Color.white)
.padding(10)
.background(chatMessage.color)
.cornerRadius(10)
Text(chatMessage.avatar)
}
}
}
}
Expand All @@ -95,8 +197,7 @@ struct ChatRow : View {
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(ChatController())
.environmentObject(ChatController())
}
}
#endif

2 changes: 1 addition & 1 deletion messenger/messengerTests/messengerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//

import XCTest
@testable import messenger


class messengerTests: XCTestCase {

Expand Down