I am currently working on a comments section modeled after TikTok's/Instagram's comment sections for a media app. The view is a sheet view that is presented as follows:
.sheet(isPresented: $showChat) {
TakesChatView(viewModel: viewModel)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.hidden)
.overlay(
VStack {
RoundedRectangle(cornerRadius: 2)
.fill(Color.gray)
.frame(width: 40, height: 5)
.padding(.top, 15)
.opacity(0.8)
Label("Chat", systemImage: "message.badge")
.lineLimit(nil)
.padding(.top, 5)
.padding([.leading, .trailing], 16)
Divider()
.padding(.top, 5)
.padding([.leading, .trailing], 16)
Spacer()
}
.frame(maxWidth: .infinity, alignment: .top)
)
}.ignoresSafeArea(.keyboard, edges: .bottom)
However, some issues arise regarding keyboard avoidance. Currently, when the user taps on the TextField to type a comment, the keyboard shifts the entire view upwards as it pops up. Instead, I need it where I can still view the comments without the keyboard affecting their placement when it pop up.
Below is the associated code for the comment view:
struct TakesChatView: View {
@ObservedObject var viewModel: TakeCommentViewModel
@FocusState var focus: Bool
@State private var selectedMedia: [PhotosPickerItem] = []
@State private var selectedImageData: [Data] = []
@State private var selectedGIFData: [Data] = []
@State private var selectedVideoData: [Data] = []
var textFieldNotEmpty: Bool {
!viewModel.textToPost.isEmpty ||
!selectedImageData.isEmpty ||
!selectedGIFData.isEmpty ||
!selectedVideoData.isEmpty
}
var body: some View {
GeometryReader { _ in
ZStack {
VStack {
contentView
commentTextField
.padding(.top,5)
}
}
}
.ignoresSafeArea(.keyboard, edges: .bottom)
.background(Color.containerBackground)
.onChange(of: viewModel.focus) { focus in
self.focus = focus
}
.onChange(of: selectedMedia) { _ in
loadMedia()
}
}
var contentView: some View {
VStack {
Spacer().frame(height: 105)
ScrollViewReader { scroll in
ScrollView(showsIndicators: true) {
ForEach(viewModel.comments, id: \.self) { comment in
TakeCommentView(
comment: comment,
viewModel: self.viewModel
)
.padding(.horizontal, 10)
.id(comment.documentID)
}
.onChange(of: viewModel.commentAdded) { id in
scroll.scrollTo(id, anchor: .bottom)
viewModel.commentAdded = nil
}
}
.scrollDismissesKeyboard(.immediately)
}
}
}
}
extension TakesChatView {
var commentTextField: some View {
VStack {
mediaPreview
HStack {
TextField("Type your comment", text: $viewModel.textToPost, axis: .vertical)
.keyboardType(.twitter)
.padding([.leading, .vertical], 6)
.focused($focus)
PhotosPicker(selection: $selectedMedia, matching: .any(of: [.images, .videos]), photoLibrary: .shared()) {
ComposeType.media.image
.frame(width: 40, height: 40, alignment: .center)
}
.onAppear {
selectedMedia.removeAll()
selectedImageData.removeAll()
selectedGIFData.removeAll()
selectedVideoData.removeAll()
}
postButton
}
.border(.lightGray, width: 1, cornerRadius: 10)
.padding([.bottom, .horizontal], 10)
}
}
}
I have tried using .ignoresSafeAres(), .safeAreaInset(), and custom keyboard observer functions but to no avail. How do I fix this issue?
Post
Replies
Boosts
Views
Activity
I am currently refactoring my app's side menu to be more like Twitter's. I have the UI down in terms of how the side menu looks and appears, but the issue is navigating to a view from the side menu. The views that a user can go to from the side menu are a mix of SwiftUI views & UIKit View Controllers. As of right now, when a user navigates to a view from the side menu, it presents it modally as a sheet. I want it to have regular navigation, where the user goes to the view displayed in full screen and can tap on the back button to go back to the previous view.
Here is the associated code:
SideMenuView.swift
SideMenuViewModel.swift
How can I modify the navigation logic to be like Twitter's? I've been stuck for days trying to find a fix but it has been a struggle.
I am working on enabling the option for users to save a video from a post in a social media app to their cameral roll. I am trying to use PHPhotoLibrary to perform the task similarly to how I did the functionality for saving images and gifs. However, when I try to perform the task with the code as is, I get the following errors:
Error Domain=PHPhotosErrorDomain Code=-1 "(null)"
The operation couldn’t be completed. (PHPhotosErrorDomain error -1.)
The implementation is as follows:
Button(action: {
guard let videoURL = URL(string: media.link.absoluteString) else {
print("Invalid video url.")
return
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
print("Video URL: \(videoURL)")
}) { (success, error) in
if let error = error {
debugPrint(error)
print(error.localizedDescription)
} else {
print("Video saved to camera roll!")
}
}
}) {
Text("Save Video")
Image(systemName: "square.and.arrow.down")
}
The video URL is successfully fetched dynamically from the post, but there's an issue with storing it locally in the library. What am I missing?
I am working on implementing tap gestures in a dynamic VideoPlayer made with AVKit. I intend to have it be when a video is viewed in a feed (this is for a social media app), the video plays without sound. Tapping on the video once enables sound, tapping on the video twice makes it full screen.
Currently, the single tap works. However, the double tap isn't detected unless I tap on the top right corner of the video.
import SwiftUI
import AVKit
struct VideoPlayerView: View {
@StateObject private var viewModel: VideoPlayerViewModel
init(url: URL, isFeedView: Bool = true) {
_viewModel = StateObject(wrappedValue: .init(url: url, isFeedView: isFeedView))
}
var body: some View {
ZStack {
if let player: AVPlayer = viewModel.player {
VideoPlayer(player: player)
.onAppear {
// Start playing or resume from the last known position if in feed view
if viewModel.isFeedView {
if let lastKnownTime = viewModel.lastKnownTime {
player.seek(to: CMTime(seconds: lastKnownTime, preferredTimescale: 600))
}
player.play()
player.volume = 0 // Set volume to 0 for feed view
}
}
.onDisappear {
// Pause the video and store the last known time
viewModel.lastKnownTime = player.currentTime().seconds
player.pause()
}
.contentShape(Rectangle())
.gesture(TapGesture(count: 2).onEnded {
print("Double tap detected")
viewModel.isFullScreen.toggle()
})
.simultaneousGesture(TapGesture().onEnded {
print("Single tap detected")
player.volume = 1 // Set volume to 1
})
}
}
.maxSize()
.fullScreenCover(isPresented: $viewModel.isFullScreen) {
AVPlayerViewControllerRepresented(viewModel: viewModel)
}
}
}
class VideoPlayerViewModel: ObservableObject {
@Published var player: AVPlayer?
@Published var lastKnownTime: Double?
@Published var isFullScreen: Bool = false
@Published var isFeedView: Bool
init(url: URL, isFeedView: Bool = true) {
player = AVPlayer(url: url)
lastKnownTime = nil
self.isFeedView = isFeedView
if isFeedView {
registerForPlaybackEndNotification()
}
}
private func registerForPlaybackEndNotification() {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil) { [weak self] _ in
self?.videoDidFinish()
}
}
private func videoDidFinish() {
// Replay logic for feed view
if isFeedView, let player = player {
player.seek(to: .zero)
player.play()
}
}
}
I tried using .contentShape(Rectangle()) as I read that it expands the detectable area for taps, but to no avail. How can I have it so that when I double tap anywhere in the video, it's detected and the video goes full screen?
I am currently refactoring the signup process for a sports media app in SwiftUI and having issues trying to have it save data associated with the new user to the backend (comprised of a mix of Firebase and a custom backend) via a signup function in an AuthService class. Whenever I enter the required info to create an account (email, username, password, etc.), I get the following errors
Error: The data couldn’t be read because it is missing.
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "user", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"user\", intValue: nil) (\"user\").", underlyingError: nil))
Here is all the relevant code:
SignUpView.swift
AuthService.swift
Signup.swift
For AuthService, the signup function is what's relevant as I call it in SignUpView and SignUp has the definition for AccountSignUp which is called in the other two files. What is not being read correctly in the data? What is missing and what can be done to fix it such that the info will be saved to the backend? All help will be greatly appreciated!
For my iOS Social Media App project, I am currently working on the Direct Messaging feature of the app. As of making this post, I have the basic functionality working where you can send messages to other users in the app. However, there are a few issues I am experiencing.
1.) When I go to the chat to send a message it shows two text input bars instead of one. It could have something to do with me also using InputBarAccessoryView but I am not sure how to go about removing one of the two input bars so there's only one.
2.) Being able to see the other user's texts and replies. I can send a message to the user but said user won't be able to see it and vice versa for some reason I haven't been able to fetch and load chat history properly.
3.) The chats don't have their own unique messages. If I send a message to one user, it shows up in my chats with other users as well rather than each chat having its own unique messages.
Here is my code associated with Messaging
import UIKit
import MessageKit
import InputBarAccessoryView
import Parse
struct Message: MessageType
{
var sender: SenderType
var messageId: String
var sentDate: Date
var kind: MessageKind
}
class ChatViewController: MessagesViewController, MessagesDataSource, MessagesLayoutDelegate, MessagesDisplayDelegate, InputBarAccessoryViewDelegate
{
let currentUser = PFUser.current()!
var otherUser: PFUser!
var messages = [MessageType]()
var inputBar = InputBarAccessoryView()
override func viewDidLoad() {
super.viewDidLoad()
// Set up the other user
let query = PFUser.query()
query?.whereKey("objectId", notEqualTo: currentUser.objectId!)
query?.findObjectsInBackground(block: { (objects, error) in
if let users = objects as? [PFUser], let user = users.first {
self.otherUser = user
// Retrieve previous messages from Parse
let messageQuery = PFQuery(className: "Message")
messageQuery.whereKey("sender", equalTo: self.currentUser)
messageQuery.whereKey("recipient", equalTo: self.otherUser!)
let recipientQuery = PFQuery(className: "Message")
recipientQuery.whereKey("sender", equalTo: self.otherUser!)
recipientQuery.whereKey("recipient", equalTo: self.currentUser)
let query = PFQuery.orQuery(withSubqueries: [messageQuery, recipientQuery])
query.findObjectsInBackground { (objects, error) in
if let messages = objects {
for message in messages {
let sender = message["sender"] as! PFUser
let text = message["text"] as! String
let sentDate = message.createdAt!
let messageKind = MessageKind.text(text)
let messageSender: Sender
do {
try sender.fetchIfNeeded()
messageSender = Sender(senderId: sender.objectId!, displayName: sender.username ?? "")
} catch {
messageSender = Sender(senderId: sender.objectId!, displayName: "Unknown")
print("Error fetching sender: \(error.localizedDescription)")
}
let message = Message(sender: messageSender, messageId: message.objectId!, sentDate: sentDate, kind: messageKind)
self.messages.append(message)
print("Fetched previous messages!")
}
self.messagesCollectionView.reloadData()
self.messagesCollectionView.scrollToLastItem(animated: false)
}
}
}
})
// Configure the messages collection view and input bar
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
view.addSubview(inputBar)
inputBar.delegate = self
inputBar.inputTextView.placeholder = "Type a message..."
inputBar.sendButton.setTitle("Send", for: .normal)
inputBar.sendButton.setTitleColor(view.tintColor, for: .normal)
inputBar.sendButton.addTarget(self, action: #selector(sendButtonPressed), for: .touchUpInside)
inputBar.translatesAutoresizingMaskIntoConstraints = false
inputBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
inputBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
inputBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
func currentSender() -> SenderType
{
return Sender(senderId: currentUser.objectId!, displayName: currentUser.username ?? "")
}
func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int
{
return messages.count
}
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType
{
return messages[indexPath.section]
}
@objc func sendButtonPressed()
{
let messageText = inputBar.inputTextView.text.trimmingCharacters(in: .whitespacesAndNewlines)
guard !messageText.isEmpty else
{
return
}
let message = Message(sender: currentSender(), messageId: UUID().uuidString, sentDate: Date(), kind: .text(messageText))
messages.append(message)
inputBar.inputTextView.text = ""
messagesCollectionView.reloadData()
messagesCollectionView.scrollToLastItem(animated: true)
print("Message sent!")
// Save the message to Parse
let parseMessage = PFObject(className: "Message")
parseMessage["sender"] = currentUser
parseMessage["recipient"] = otherUser
parseMessage["text"] = messageText
parseMessage.saveInBackground()
print("Message saved!")
}
func inputBar(_ inputBar: InputBarAccessoryView, textViewTextDidChangeTo text: String)
{
if text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
inputBar.sendButton.isEnabled = false
} else
{
inputBar.sendButton.isEnabled = true
}
}
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String)
{
sendButtonPressed()
}
}
The above code is for all the messaging functionality such as sending texts, receiving messages, and loading and showing chat history. All help would be greatly appreciated!
I am working on in-app notifications for my social media app that I'm programming using Parse and UIKit on Xcode 14.2. My main issue is that while the notification system itself is working, it's not varying by the user.
For example, say there are two users, User1 and User2. The way the notifications should be set up is when User1 likes User2's post or leaves a comment on User2's post, only User2 gets a notification saying "User1 liked your post!" or "User1 left a comment on your post!". However as of right now, when User1 likes or comment's on User2's post, User1 gets the notification saying "User1 liked your post!".
Here is the code that implements notifications in my app:
import UIKit
import Parse
class NotificationViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
@IBOutlet weak var tableView: UITableView!
var notifications: [PFObject] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(didLikePost(_:)), name: NSNotification.Name("PostLikedByOtherUser"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(madeCommentOnPost(_:)), name: NSNotification.Name("CommentMade"), object: nil)
let notificationClass = PFObject(className: "Notification")
notificationClass["user"] = PFUser.current()
notificationClass["fromUser"] = PFUser.current()
notificationClass["post"] = PFObject(className: "Post")
notificationClass.saveInBackground { (success, error) in
if success {
print("Notification class created successfully")
} else if let error = error {
print("Error creating notification class: \(error.localizedDescription)")
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let query = PFQuery(className: "Notification")
query.whereKey("user", equalTo: PFUser.current()!)
query.includeKey("fromUser")
query.includeKey("post")
query.order(byDescending: "createdAt")
query.findObjectsInBackground { (objects, error) in
if let error = error {
print("Error querying for notifications: \(error.localizedDescription)")
} else if let notifications = objects {
self.notifications = notifications
self.tableView.reloadData()
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notifications.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "NotificationCell", for: indexPath) as! NotificationCell
let notification = notifications[indexPath.row]
let fromUser = notification["fromUser"] as! PFUser
let post = notification["post"] as? PFObject
cell.usernameLabel.text = fromUser.username
if let username = fromUser.username, let post = post {
if let likes = post["likes"] as? [PFObject], let _ = likes.first(where: { $0["liker"] as? String == PFUser.current()?.objectId }),
post["user"] as? String == PFUser.current()?.objectId {
cell.messageLabel.text = "You liked your own post."
} else if let comments = post["comments"] as? [PFObject], let _ = comments.first(where: { $0["commenter"] as? String == PFUser.current()?.objectId }) {
cell.messageLabel.text = "You liked \(username)'s post."
} else if let _ = notification["comment"] {
cell.messageLabel.text = "\(username) commented on your post."
} else {
cell.messageLabel.text = "\(username) liked your post."
}
}
return cell
}
@objc func didLikePost(_ notification: Notification)
{
guard let post = notification.object as? PFObject,
let postOwner = post["user"] as? PFUser,
let liker = notification.userInfo?["liker"] as? PFUser,
postOwner.objectId != liker.objectId,
postOwner.objectId == PFUser.current()?.objectId else
{
return
}
let notificationObject = PFObject(className: "Notification")
notificationObject["user"] = postOwner
notificationObject["fromUser"] = liker
notificationObject["post"] = post
notificationObject.saveInBackground { (success, error) in
if success
{
print("Successfully created notification")
} else if let error = error
{
print("Error creating notification: \(error.localizedDescription)")
}
}
}
@objc func madeCommentOnPost(_ notification: Notification)
{
guard let comment = notification.object as? PFObject,
let post = comment["post"] as? PFObject,
let postOwner = post["user"] as? PFUser,
let commenter = notification.userInfo?["commenter"] as? PFUser,
postOwner.objectId != commenter.objectId,
postOwner.objectId == PFUser.current()?.objectId else
{
return
}
let notificationObject = PFObject(className: "Notification")
notificationObject["user"] = postOwner
notificationObject["fromUser"] = commenter
notificationObject["post"] = post
notificationObject.saveInBackground { (success, error) in
if success
{
print("Successfully created notification")
} else if let error = error
{
print("Error creating notification: \(error.localizedDescription)")
}
}
}
deinit
{
NotificationCenter.default.removeObserver(self)
}
}
I would really appreciate all the help on here.
I am currently working on a social media app and so far I have been able to make a LoginViewController & a SignUpVIewController. Right now I am currently trying to have it where on the SignUpViewController if not all required fields are filled out then the app will display an alert. However, every time i run the app the alert doesn't show up on the simulator screen. Same goes for Xcode's terminal.
My code for the SignUpViewController is as follows:
import UIKit
import Parse
class SignUpViewController: UIViewController
{
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var confirmPasswordFieldText: UITextField!
@IBOutlet weak var signUpButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func signUpButtonTapped(_ sender: UIButton)
{
let username = usernameTextField.text
let password = passwordTextField.text
let email = emailTextField.text
let confirmpw = confirmPasswordFieldText.text
if username == "" || password == "" || email == "" || confirmpw == ""
{
let alert = UIAlertController(title: "Error", message: "Please fill out all required fields", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default,handler: nil))
present(alert, animated: true, completion: nil)
return
}
let newUser = User(username: username!, password: password!, email: email!)
}
}
class User
{
var username: String
var password: String
var email: String
init(username: String, password: String, email: String) {
self.username = username
self.password = password
self.email = email
}
}
All the buttons are connected to the correct spots. Would greatly appreciate if anyone can help me fix this.
I am doing the Swift Landmarks app tutorial on Xcode 13.2.1 on my 2019 Macbook Pro. I am up to the Working with UI Controls section. Every time I add the following lines of code to ProfileSummary.swift:
@EnvironmentObject var modelData: ModelData
.
.
.
Divider()
VStack(alignment: .leading)
{
Text("Recent Hikes")
.font(.headline)
HikeView(hike: modelData.hikes[0])
}
and I then try to view the preview I repeatedly get a crash report and everything stops working. When I remove those lines of code everything goes back to working fine.
The crash report is in this attachment:
Translated Crash Report
^ That is the translated report. If you guys would like the full report I will post it. I am stuck and don't know what to do any help will be appreciated!
In the fps im making, I am now working on mapping the levels. I have already made the walls and provided texture on them, and now I'm doing the same to the floor. I am able to code a red floor, however whenever I try to code texture on it I get the following error:
"Thread 1: Swift runtime failure: Unexpectedly found nil while unwrapping an Optional value"
The code appears at this line in ViewController.swift
return Textures(loader: { name in Bitmap(image: UIImage(named: name)!)!})
Here's the full code for ViewController.swift:
import Engine
private let joystickRadius: Double = 40
private let maximumTimeStep: Double = 1 / 20
private let worldTimeStep: Double = 1 / 120
private func loadTextures() -> Textures
{
return Textures(loader: { name in Bitmap(image: UIImage(named: name)!)!})
}
private func loadMap() -> Tilemap {
let jsonURL = Bundle.main.url(forResource: "Map", withExtension: "json")!
let jsonData = try! Data(contentsOf: jsonURL)
return try! JSONDecoder().decode(Tilemap.self, from: jsonData)
}
class ViewController: UIViewController {
private let imageView = UIImageView()
private let panGesture = UIPanGestureRecognizer()
private var world = World(map: loadMap())
private var lastFrameTime = CACurrentMediaTime()
private let textures = loadTextures()
override func viewDidLoad() {
super.viewDidLoad()
setUpImageView()
let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.add(to: .main, forMode: .common)
view.addGestureRecognizer(panGesture)
}
private var inputVector: Vector {
switch panGesture.state {
case .began, .changed:
let translation = panGesture.translation(in: view)
var vector = Vector(x: Double(translation.x), y: Double(translation.y))
vector /= max(joystickRadius, vector.length)
panGesture.setTranslation(CGPoint(
x: vector.x * joystickRadius,
y: vector.y * joystickRadius
), in: view)
return vector
default:
return Vector(x: 0, y: 0)
}
}
@objc func update(_ displayLink: CADisplayLink) {
let timeStep = min(maximumTimeStep, displayLink.timestamp - lastFrameTime)
let inputVector = self.inputVector
let rotation = inputVector.x * world.player.turningSpeed * worldTimeStep
let input = Input(speed: -inputVector.y, rotation: Rotation(sine: sin(rotation), cosine: cos(rotation)))
let worldSteps = (timeStep / worldTimeStep).rounded(.up)
for _ in 0 ..< Int(worldSteps) {
world.update(timeStep: timeStep / worldSteps, input: input)
}
lastFrameTime = displayLink.timestamp
let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
var renderer = Renderer(width: width, height: height, textures: textures)
renderer.draw(world)
imageView.image = UIImage(bitmap: renderer.bitmap)
}
func setUpImageView() {
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
imageView.layer.magnificationFilter = .nearest
}
}
Here's the code for other files that deal with the texturing of the floor and the level in general:
Textures.swift
{
case wall, wall2
case floor, ceiling
}
public struct Textures
{
private let textures: [Texture: Bitmap]
}
public extension Textures
{
init(loader: (String) -> Bitmap)
{
var textures = [Texture: Bitmap]()
for texture in Texture.allCases
{
textures[texture] = loader(texture.rawValue)
}
self.init(textures: textures)
}
subscript(_ texture: Texture) -> Bitmap
{
return textures[texture]!
}
}
Renderer.swift:
public private(set) var bitmap: Bitmap
private let textures: Textures
public init(width: Int, height: Int, textures: Textures) {
self.bitmap = Bitmap(width: width, height: height, color: .black)
self.textures = textures
}
}
public extension Renderer {
mutating func draw(_ world: World) {
let focalLength = 1.0
let viewWidth = Double(bitmap.width) / Double(bitmap.height)
let viewPlane = world.player.direction.orthogonal * viewWidth
let viewCenter = world.player.position + world.player.direction * focalLength
let viewStart = viewCenter - viewPlane / 2
// Cast rays
let columns = bitmap.width
let step = viewPlane / Double(columns)
var columnPosition = viewStart
for x in 0 ..< columns {
let rayDirection = columnPosition - world.player.position
let viewPlaneDistance = rayDirection.length
let ray = Ray(
origin: world.player.position,
direction: rayDirection / viewPlaneDistance
)
let end = world.map.hitTest(ray)
let wallDistance = (end - ray.origin).length
// Draw wall
let wallHeight = 1.0
let distanceRatio = viewPlaneDistance / focalLength
let perpendicular = wallDistance / distanceRatio
let height = wallHeight * focalLength / perpendicular * Double(bitmap.height)
let wallTexture: Bitmap
let wallX: Double
if end.x.rounded(.down) == end.x
{
wallTexture = textures[.wall]
wallX = end.y - end.y.rounded(.down)
}
else
{
wallTexture = textures[.wall2]
wallX = end.x - end.x.rounded(.down)
}
let textureX = Int(wallX * Double(wallTexture.width))
let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 - 0.001)
bitmap.drawColumn(textureX, of: wallTexture, at: wallStart, height: height)
// Draw floor
let floorTexture = textures[.floor]
let floorStart = Int(wallStart.y + height) + 1
for y in min(floorStart, bitmap.height) ..< bitmap.height {
let normalizedY = (Double(y) / Double(bitmap.height)) * 2 - 1
let perpendicular = wallHeight * focalLength / normalizedY
let distance = perpendicular * distanceRatio
let mapPosition = ray.origin + ray.direction * distance
let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
bitmap[x, y] = floorTexture[textureX, textureY]
}
columnPosition += step
}
}
}
The rest of the code can be found in the link below:
https://github.com/KingDecorpel12/RampageFPS/tree/main/RetroRampage/Source
Any and all help will be greatly appreciated!
In the fps I'm making, I am making a 2D maze for the avatar to navigate in. I just finished coding it however whenever I compile it the app instantly crashes.
The error that makes it crash is the following "Thread 1: Swift runtime failure: Range requires lowerBound <= upperBound"
and occurs in this file
Bitmap.swift
import UIKit
public struct Bitmap
{
public private(set) var pixels: [Color]
public let width: Int
public init(width: Int, pixels: [Color], height: Int)
{
self.width = width
self.pixels = pixels
}
}
public extension Bitmap
{
var height: Int
{
let bitmapheight = pixels.count / width
return bitmapheight
}
subscript(x: Int, y: Int) -> Color
{
get { return pixels[y * width + x] }
set {
guard x >= 0, y >= 0, x < width, y < height else {
return
}
pixels[y * width + x] = newValue}
}
init(width: Int, height: Int, color: Color) {
self.pixels = Array(repeating: color, count: width * height)
self.width = width
}
mutating func fill(rect: Rect, color: Color)
{
for y in Int(rect.min.y) ..< Int(rect.max.y)
{
for x in Int(rect.min.x) ..< Int(rect.max.x)
{
self[x, y] = color
}
}
}
}
at the line
for x in Int(rect.min.x) ..< Int(rect.max.x)
When I put a breakout on the line and run the code I see the following when the compiler runs the line:
min Engine.Vector x = (Double) 51.75 y = (Double) 0 max Engine.Vector x = (Double) 2 y = (Double) 51.75
I also get this at the terminal:
Engine was compiled with optimization - stepping may behave oddly; variables may not be available.
Since it's a big project and there's multiple files calling from one another in both the main game files and the game engine files, you can find the rest of the source code in the link below:
https://github.com/KingDecorpel12/RampageFPS
Game Engine files, including Bitmap.swift, will be in Rampage/Source/Engine while the main game files will be in Rampage/Source/Rampage.
Any and all help will be greatly appreciated!.
I am coding a basic fps app on Xcode 12.5. As of right now, Im working on a file that acts as a bridge between UIKit and the game engine used for this game.
The code is provided below:
Bitmap.swift
import UIKit
public struct Bitmap
{
public private(set) var pixels: [Color]
public let width: Int
public init(width: Int, pixels: [Color])
{
self.width = width
self.pixels = pixels
}
}
public extension Bitmap
{
var height: Int
{
return pixels.count / width
}
subscript(x: Int, y: Int) -> Color
{
get { return pixels[y * width + x] }
set { pixels[y * width + x] = newValue}
}
init(width: Int, height: Int, color: Color) {
self.pixels = Array(repeating: color, count: width * height)
self.width = width
}
}
UIImage+Bitmap.swift
import UIKit
import Engine
extension UIImage {
convenience init?(bitmap: Bitmap) {
let alphaInfo = CGImageAlphaInfo.premultipliedLast
let bytesPerPixel = MemoryLayout<Color>.size
let bytesPerRow = bitmap.width * bytesPerPixel
guard let providerRef = CGDataProvider(data: Data(
bytes: bitmap.pixels, count: bitmap.height * bytesPerRow
) as CFData) else {
return nil
}
guard let cgImage = CGImage(
width: bitmap.width,
height: bitmap.height,
bitsPerComponent: 8,
bitsPerPixel: bytesPerPixel * 8,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: alphaInfo.rawValue),
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent
) else {
return nil
}
self.init(bitmap: cgImage)
}
}
For my UIImage+Bitmap.swift code I get the following error:
"'height' is inaccessible due to 'internal' protection level"
Any help will be appreciated!
I'm working on a simple anime app project where you can view a list of the top anime, search for any anime, add them to either a watched, to watch or watching section as well view the info (such as synopsis for example) when clicking on an anime. For the most part, everything works except for viewing the anime info from the search results, an example being not able to view the info of Naruto when clicking on it in search results. My code is in the attachment below.
Continued