Can someone tell me why on line 9 I get the error Generic parameter 'Label' could not be inferred
struct PlayerControlsView : View {
#State var playerPaused = true
#State var seekPos = 0.0
let player: AVPlayer
var body: some View {
HStack {
Button(action: {
self.playerPaused.toggle()
if self.playerPaused {
self.player.pause()
}
else {
self.player.play()
}
}) {
Image(systemName: playerPaused ? "play" : "pause")
.padding(.leading, CGFloat(20))
.padding(.trailing, CGFloat(20))
}
Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
guard let item = self.player.currentItem else {
return
}
let targetTime = self.seekPos * item.duration.seconds
self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
})
.padding(.trailing, CGFloat(20))
}
}
}
And of course how to fix it.
Your Slider init is incorrect. There is no version that has from: and through:. in: [0...1] is the default, though, so you don't need it anyway.
Slider(value: $seekPos, onEditingChanged: { _ in
SwiftUI error messages are generally useless. The way you find the error is to keep commenting out things until it compiles, and then add them back in.
However, what you're doing here isn't going to work. You can't put a AVPlayer inside a View. A View is a struct, and they get created, copied, and destroyed all the time. See How to stream remote audio in iOS 13? (SwiftUI).
Related
I have a CameraView in my app that I'd like to bring up whenever a button is to be presssed. It's a custom view that looks like this
// The CameraView
struct Camera: View {
#StateObject var model = CameraViewModel()
#State var currentZoomFactor: CGFloat = 1.0
#Binding var showCameraView: Bool
// MARK: [main body starts here]
var body: some View {
GeometryReader { reader in
ZStack {
// This black background lies behind everything.
Color.black.edgesIgnoringSafeArea(.all)
CameraViewfinder(session: model.session)
.onAppear {
model.configure()
}
.alert(isPresented: $model.showAlertError, content: {
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
model.alertError.primaryAction?()
}))
})
.scaledToFill()
.ignoresSafeArea()
.frame(width: reader.size.width,height: reader.size.height )
// Buttons and controls on top of the CameraViewfinder
VStack {
HStack {
Button {
//
} label: {
Image(systemName: "xmark")
.resizable()
.frame(width: 20, height: 20)
.tint(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
Spacer()
flashButton
}
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
Spacer()
flipCameraButton
}
.padding([.horizontal, .bottom], 20)
.frame(maxHeight: .infinity, alignment: .bottom)
}
} // [ZStack Ends Here]
} // [Geometry Reader Ends here]
} // [Main Body Ends here]
// More view component code goes here but I've excluded it all for brevity (they don't add anything substantial to the question being asked.
} // [End of CameraView]
It contains a CameraViewfinder View which conforms to the UIViewRepresentable Protocol:
struct CameraViewfinder: UIViewRepresentable {
class VideoPreviewView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
let session: AVCaptureSession
func makeUIView(context: Context) -> VideoPreviewView {
let view = VideoPreviewView()
view.backgroundColor = .black
view.videoPreviewLayer.cornerRadius = 0
view.videoPreviewLayer.session = session
view.videoPreviewLayer.connection?.videoOrientation = .portrait
return view
}
func updateUIView(_ uiView: VideoPreviewView, context: Context) {
}
}
I wish to add a binding property to this camera view that allows me to toggle this view in and out of my screen like any other social media app would allow. Here's an example
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView(showCamera: $showCamera)
}
}
I understand that the code to achieve this must be written inside the updateUIView() method. Now, although I'm quite familiar with SwiftUI, I'm relatively inexperienced with UIKit, so any help on this and any helpful resources that could help me better code situations similar to this would be greatly appreciated.
Thank you.
EDIT: Made it clear that the first block of code is my CameraView.
EDIT2: Added Example of how I'd like to use the CameraView in my App.
Judging by the way you would like to use it in the app, the issue seems to not be with the CameraViewFinder but rather with the way in which you want to present it.
A proper SwiftUI way to achieve this would be to use a sheet like this:
#State var showCamera: Bool = false
var body: some View {
mainTabView
.sheet(isPresented: $showCamera) {
CameraView()
.interactiveDismissDisabled() // Disables swipe to dismiss
}
}
If you don't want to use the sheet presentation and would like to cover the whole screen instead, then you should use the .fullScreenCover() modifier like this.
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView()
.fullScreenCover(isPresented: $showCamera)
}
}
Either way you would need to somehow pass the state to your CameraView to allow the presented screen to set the state to false and therefore dismiss itself, e.g. with a button press.
I have a chat app, where whenever a chat room is opened, I need the view to scroll to the bottom as soon as the messages are fetched.
The thing is that although it does scroll perfectly when a new message is received or sent (see ViewModel down below), it is very jittery when I tell it to scroll right after the first batch of messages is fetched, which happens once as soon as the view appears.
After a lot of trial and error, I realized that if I add a small delay to the scroll, it'll improve but not completely! It is like it's trying to scroll to the very bottom, but it'll fail just for a few inches. I also realized that if I add a bigger delay, like 2 seconds, it'll scroll just fine.
Here's the messages list view:
struct MessagesView: View {
#StateObject private var viewModel = ViewModel()
// -----------------------
let currentChatRoom: ChatRoom
// -----------------------
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
VStack {
ScrollView {
ScrollViewReader { proxy in
LazyVStack {
ForEach(viewModel.messages) { message in
MessageView(message: message)
.id(message.id)
.onTapGesture {
viewModel.shouldDismissKeyboard = true
}
}
}
.onChange(of: viewModel.shouldScrollToMessageId) { messageId in
if let messageId = messageId, !messageId.isEmpty {
proxy.scrollTo(messageId, anchor: .bottom)
}
}
}
}
VStack(alignment: .leading) {
if chatEnvironment.isOtherUserTyping {
TypingIndicationView()
}
BottomView()
.padding(.bottom, 4)
}
}
}
.onAppear {
viewModel.setUp(currentChatRoom: currentChatRoom)
}
}
}
As you can see, it’s viewModel.shouldScrollToMessageId that’s responsible for "auto-scrolling" to the last message.
Here’s MessageView:
fileprivate struct MessageView: View {
let message: Message
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 1) {
Text(message.user.isCurrentUser == true ? "You" : "\(message.user.username)")
.foregroundColor(message.user.isCurrentUser == true ? .customGreen : .customBlue)
.multilineTextAlignment(.leading)
.font(.default(size: 16))
.padding(.bottom, 1)
if let imageURL = message.postSource?.imageURL, !imageURL.isEmpty {
VStack(alignment: .leading) {
WebImage(url: .init(string: imageURL))
.resizable()
.indicator(.activity)
.scaledToFill()
.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 1.45)
.cornerRadius(25)
}
}
Text(message.text)
.foregroundColor(.white)
.multilineTextAlignment(.leading)
.font(.default(size: 16))
}
Spacer()
}
.padding(.bottom, 8)
.padding(.horizontal)
.background(
Color.black
)
}
}
Here’s the ViewModel:
class ViewModel: ObservableObject {
#Published var messages = [Message]()
#Published var text = ""
#Published var shouldScrollToMessageId: String?
#Published var currentChatRoom: ChatRoom?
// -----------------------------
private var isInitialized = false
// -----------------------------
func setUp(currentChatRoom: ChatRoom) {
guard !isInitialized else { return }
isInitialized.toggle()
// -----------------------------
self.currentChatRoom = currentChatRoom
// -----------------------------
getFirstBatchOfMessages(chatRoom: chatRoom)
subscribeToNewMessages()
}
private func getFirstBatchOfMessages(chatRoom: ChatRoom) {
messagesService.getMessages(chatRoomId: chatRoom.id) { [weak self] messages in
DispatchQueue.main.async {
self?.messages = messages
}
self?.scrollToBottom(delay: 0.15)
}
}
private func subscribeToNewMessages() {
...
if !newMessages.isEmpty {
self?.scrollToBottom(delay: 0)
}
}
func scrollToBottom(delay: TimeInterval) {
DispatchQueue.main.async {
self.shouldScrollToMessageId = self.messages.last?.id
}
}
func sendMessage() {
...
scrollToBottom(delay: 0)
}
}
Here, scrollToBottom is responsible for notifying the MessagesView that shouldScrollToMessageId's value changed and that it should scroll to the last message.
Any help will be much appreciated!!!
I am also writing an application with a chat on SwiftUI and I also have a lot of headaches with a bunch of ScrollView and LazyVStack.
In my case, I load messages from the CoreData and display them in a LazyVStack, so in my case, scrolling to the last message does not work, it seems to me simply because a bottom last view did not render, because rendering starts from the top.
Therefore, I came up with a solution with placing an invisible view at the bottom and assigned it a static ID, in my case -1:
VStack(spacing: 0) {
LazyVStack(spacing: 0) {
ForEach(messages) { message in
MessageRowView(viewWidth: wholeViewProxy.size.width, message: message)
.equatable()
}
}
Color.clear.id(-1)
.padding(.bottom, inputViewHeight)
}
I call scroll to this view:
.onAppear {
scrollTo(messageID: -1, animation: nil, scrollReader: scrollReader)
}
And it works... but sometimes...
Sometimes it works correctly, sometimes it stops without scrolling a couple of screens to the end. Looks like LazyVStack is rendering the next views after the scrollTo has finished its work. Also, if add scrolling with some delay it may works better, but not always perfect.
I hope this can be a little helpful and if you find a stable solution I will be very happy if you share :)
I have the following code that, when tapped it will add the number of likes to a post, it will only allow the user to tap it once, works well but when I reload the app I can like it again, been trying to workout the best way to save that it has been tapped already. I have added the button state as false:
#State var buttonTapped = false
Button(action:
{
self.buttonTapped.toggle() //only allow one tap
let like = Int.init(post.likes)!
ref.collection("Posts").document(post.id).updateData(["likes": "\(like
+ 1)"]) { (err) in
if err != nil{
print((err!.localizedDescription))
return
}
// postData.getAllPosts()
print("updated...")
}
}
) {
Image(systemName: "flame")
.resizable()
.frame(width: 20, height: 20)
}.disabled(buttonTapped)
Any pointers in the right direction would be greatly appreciated
You can use UserDefaults to store the value
struct ContentView : View {
#State var buttonTapped : Bool = UserDefaults.standard.bool(forKey: "buttonTapped")
var body : some View {
Button(action: {
UserDefaults.standard.set(true, forKey: "buttonTapped")
buttonTapped.toggle()
}) {
Image(systemName: "flame")
.resizable()
.frame(width: 20, height: 20)
}.disabled(buttonTapped)
}
}
I have a 3-part picker, and I'm trying to make the values of one Picker to be based on the value of another. Specifically adding/removing the s on the end of "Days","Weeks",etc. I have read a similar post (here) on this type of situation, but the proposed Apple solution for IOS 14+ deployments is not working. Given that the other question focuses primarily on pre-14 solutions, I thought starting a new question would be more helpful.
Can anyone shed any light on why the .onChange is never getting called? I set a breakpoint there, and it is never called when the middle wheels value change between 1 and any other value as it should.
The unconventional init is just so I could encapsulate this code removed from a larger project.
Also, I have the .id for the 3rd picker commented out in the code below, but can un-comment if the only problem remaining is for the 3rd picker to update on the change.
import SwiftUI
enum EveryType:String, Codable, CaseIterable, Identifiable {
case every="Every"
case onceIn="Once in"
var id: EveryType {self}
var description:String {
get {
return self.rawValue
}
}
}
enum EveryInterval:String, Codable, CaseIterable, Identifiable {
case days = "Day"
case weeks = "Week"
case months = "Month"
case years = "Year"
var id: EveryInterval {self}
var description:String {
get {
return self.rawValue
}
}
}
struct EventItem {
var everyType:EveryType = .onceIn
var everyInterval:EveryInterval = .days
var everyNumber:Int = Int.random(in:1...3)
}
struct ContentView: View {
init(eventItem:Binding<EventItem> = .constant(EventItem())) {
_eventItem = eventItem
}
#Binding var eventItem:EventItem
#State var intervalId:UUID = UUID()
var body: some View {
GeometryReader { geometry in
HStack {
Picker("", selection: self.$eventItem.everyType) {
ForEach(EveryType.allCases)
{ type in Text(type.description)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width * 0.3, height:100)
.compositingGroup()
.padding(0)
.clipped()
Picker("", selection: self.$eventItem.everyNumber
) {
ForEach(1..<180, id: \.self) { number in
Text(String(number)).tag(number)
}
}
//The purpase of the == 1 below is to only fire if the
// everyNumber values changes between being a 1 and
// any other value.
.onChange(of: self.eventItem.everyNumber == 1) { _ in
intervalId = UUID() //Why won't this ever happen?
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width * 0.25, height:100)
.compositingGroup()
.padding(0)
.clipped()
Picker("", selection: self.$eventItem.everyInterval) {
ForEach(EveryInterval.allCases) { interval in
Text("\(interval.description)\(self.eventItem.everyNumber == 1 ? "" : "s")")
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width * 0.4, height:100)
.compositingGroup()
.clipped()
//.id(self.intervalId)
}
}
.frame(height:100)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(eventItem: .constant(EventItem()))
}
}
For Picker, its item data type must conform Identifiable and we must pass a property of item into "tag" modifier as "id" to let Picker trigger selection and return that property in Binding variable with selection.
For example :
Picker(selection: $selected, label: Text("")){
ForEach(data){item in //data's item type must conform Identifiable
HStack{
//item view
}
.tag(item.property)
}
}
.onChange(of: selected, perform: { value in
//handle value of selected here (selected = item.property when user change selection)
})
//omg! I spent whole one day to find out this
Try the following
.onChange(of: self.eventItem.everyNumber) { newValue in
if newValue == 1 {
intervalId = UUID()
}
}
but it might also depend on how do you use this view, because with .constant binding nothing will change ever.
The answer by Thang Dang, above, turned out to be very helpful to me. I did not know how to conform my tag to Identifiable, but changed my tags from tag(1) to a string, as in the SwiftUI code below. The tag with a mere number in it caused nothing to happen when the Picker was set to Icosahedron (my breakpoint on setShape was never triggered), but the other three caused the correct shape to be passed in to setShape.
// set the current Shape
func setShape(value: String) {
print(value)
}
#State var shapeSelected = "Cube"
VStack {
Picker(selection: $shapeSelected, label: Text("$\(shapeSelected)")) {
Text("Cube").tag("Cube")
Text("Simplex").tag("Simplex")
Text("Pentagon (3D)").tag("Pentagon")
Text("Icosahedron").tag(1)
}.onChange(of: shapeSelected, perform: { tag in
setShape(value: "\(tag)")
})
}
i am using a Picker to show a segmented control and wish to know when the picker value changes so i can perform a non-UI action. Using the proposed onReceive() modifier (as suggested here) does not work as it is called every time the body is rendered.
Here's the code i have:
struct PickerView: View {
#State private var weather = 0
#State private var showMessage = false
var body: some View {
VStack(spacing: 24) {
Picker(selection: $weather, label: Text("Weather")) {
Image(systemName: "sun.max.fill").tag(0)
Image(systemName: "cloud.sun.rain.fill").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.frame(width: 120, height: 48)
.onReceive([weather].publisher.first()) { connectionType in
print("connection type is: \(connectionType)")
}
Button(action: { self.showMessage.toggle() }) {
Text("Press Me")
}
if showMessage {
Text("Hello World")
}
}
}
}
The onReceive() block will get called any time the body is rendered, including the first time and any time the button (which toggles showing a message) is pressed.
Any ideas why this is happening and how i can only react to when the picker value is changed?
Here is possible solution instead of .onReceive
Picker(selection: Binding( // << proxy binding
get: { self.weather },
set: { self.weather = $0
print("connection type is: \($0)") // side-effect
})
, label: Text("Weather")) {
Image(systemName: "sun.max.fill").tag(0)
Image(systemName: "cloud.sun.rain.fill").tag(1)
}