Play audio on touch down action of button in swiftui - ios

I want to play a audio when a button is touched down(as soon as it is clicked) not on touch release in SwiftUI. How can I implement this?
My code looks something like this:
struct PressedButtonStyle: ButtonStyle {
let touchDown: () -> ()
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? Color.gray : Color.blue)
.background(configuration.isPressed ? self.handlePressed() : Color.clear)
}
private func handlePressed() -> Color {
touchDown()
return Color.clear
}
}
struct DemoPressedButton: View {
#State var audioPlayer: AVAudioPlayer!
var body: some View {
Button("Demo") {
print(">> tap up")
}
.buttonStyle(PressedButtonStyle {
print(">> tap down")
let sound = Bundle.main.path(forResource: "filename", ofType: "wav")
self.audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: sound!)) // receives warning about changing state while updating view
self.audioPlayer.play() // breaks
})
}
}
The code breaks when calling self.audioPlayer.play() .
The custom touchdown code is from this link: SwiftUI button action as soon as button is clicked not on click release

Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4
Note: you should keep reference to AVAudioPlayer while it is playing and better track its state, so this is more appropriate to do in some helper class (like view model)
class PlayViewModel {
private var audioPlayer: AVAudioPlayer!
func play() {
let sound = Bundle.main.path(forResource: "filename", ofType: "wav")
self.audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: sound!))
self.audioPlayer.play()
}
}
struct DemoPressedButton: View {
let vm = PlayViewModel()
var body: some View {
Button("Demo") {
print(">> tap up")
}
.buttonStyle(PressedButtonStyle {
print(">> tap down")
self.vm.play()
})
}
}

Related

How to make a custom AVPlayer support SharePlay adjust coordinate or play-pause control

This app was written using SwiftUI and I also consulted Apple's official documentation and some third-party websites. So I wrote something like the following code
This is the Swift code where the player is called and the SharePlay-related logic
import SwiftUI
import AVFoundation
import AVKit
import GroupActivities
import Combine
struct EpisodeDetail: View {
#State var episode: CommonResponse<Episode>
#State var videoPlayer: CustomVideoPlayer
#State private var groupSession: GroupSession<EpisodeActivity>?
#State private var subscriptions = Set<AnyCancellable>()
var body: some View {
VStack {
videoPlayer
.transition(.move(edge: .bottom))
.edgesIgnoringSafeArea(.all)
ScrollView {
VStack(alignment: .center) {
Button {
prepareToPlay(episode.attributes)
} label: {
Label("同播共享", systemImage: "shareplay")
}
.buttonStyle(.bordered)
.cornerRadius(20)
}
VStack(alignment: .leading, spacing: 20) {
Text(episode.attributes.title)
.font(.title2)
.fontWeight(.bold)
Text(episode.attributes.description)
.font(.body)
.foregroundColor(.gray)
}
}
.padding(12)
}
.task {
for await session in EpisodeActivity.sessions() {
configureGroupSession(session)
}
}
}
private func configureGroupSession(_ session: GroupSession<EpisodeActivity>) {
groupSession = session
videoPlayer.player.playbackCoordinator.coordinateWithSession(session)
session.$state
.sink { state in
if case .invalidated = state {
groupSession = nil
subscriptions.removeAll()
}
}
.store(in: &subscriptions)
session.$activity
.sink { activity in
print("Activity Changed: \(activity.metadata.title ?? "No title for this shitty video LOL")")
}
.store(in: &subscriptions)
session.join()
}
private func prepareToPlay(_ playerEpisodeData: Episode) {
let activity = EpisodeActivity(episode: playerEpisodeData)
Task {
switch await activity.prepareForActivation() {
case .activationDisabled:
videoPlayer.player.replaceCurrentItem(with: AVPlayerItem(url: playerEpisodeData.videoUrl))
break
case .activationPreferred:
videoPlayer.player.replaceCurrentItem(with: AVPlayerItem(url: playerEpisodeData.videoUrl))
_ = try await activity.activate()
case .cancelled:
break
#unknown default:
break
}
}
}
}
Then the following code is a custom AVPlayer
import SwiftUI
import AVFoundation
import AVKit
struct CustomVideoPlayer: UIViewControllerRepresentable {
#State var videoUrl: URL
var player: AVPlayer {
return AVPlayer(url: videoUrl)
}
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
playerController.modalPresentationStyle = .fullScreen
playerController.allowsPictureInPicturePlayback = true
playerController.canStartPictureInPictureAutomaticallyFromInline = true
playerController.player = player
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
return AVPlayerViewController()
}
}
The SharePlay prompt pops up properly and shows the current activity correctly
This is a screenshot of the SharePlay pop-up box, I couldn't insert an image, so I had to insert a link
https://i.stack.imgur.com/QNqBE.jpg
But when I do something with the player, like pause, or adjust the playback progress, the other iPhone doesn't sync
So what should I do?
Can't thank you enough :-)
Solved, by adding #State annotation to AVPlayer, thanks guys, this question is closed
struct CustomVideoPlayer: UIViewControllerRepresentable {
#State var player: AVPlayer? = nil
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
playerController.modalPresentationStyle = .fullScreen
playerController.allowsPictureInPicturePlayback = true
playerController.canStartPictureInPictureAutomaticallyFromInline = true
playerController.player = player
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
return AVPlayerViewController()
}
}

Playback controls in swiftui

Attempting to use AVKit's AVPlayer to play a video without playback controls:
private let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "videoToPlay", ofType: "mp4")!))
player.showsPlaybackControls = false
The above results in an error message that declarations must be separated by ';'.
I've also tried:
VideoPlayer(player: player)
.onAppear {
player.showsPlaybackControls = false
}
Which results in a different error.
Any advice on hiding playback controls with swiftui?
VideoPlayer does not appear to have any methods on it that I can find in the documentation to control whether the playback controls are shown.
showPlaybackControls doesn't work because AVPlayer doesn't have that property either.
It looks like for now you'll have to do something like wrap an AVPlayerViewController:
Note that this is a very limited example and doesn't take into account a lot of scenarios you may need to consider, like what happens when AVPlayerControllerRepresented gets reloaded because it's parent changes -- will you need to use updateUIViewController to update its properties? You will also probably need a more stable solution to storing your AVPlayer than what I used, which will also get recreated any time the parent view changes. But, all of these are relatively easily solved architectural decisions (look into ObservableObject, StateObject, Equatable, etc)
struct ContentView: View {
let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "IMG_0226", ofType: "mp4")!))
var body: some View {
AVPlayerControllerRepresented(player: player)
.onAppear {
player.play()
}
}
}
struct AVPlayerControllerRepresented : UIViewControllerRepresentable {
var player : AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
}
For me, setting allowsHitTesting false hides play back controls in iOS 16:
private let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "videoToPlay", ofType: "mp4")!))
...
// Disable user interaction on the video player to hide playback controls
VideoPlayer(player: player)
.allowsHitTesting(false)
There may be some corner cases I'm missing where the UI could still appear, but I have yet to encounter that.
I was having problems with playing segments with VideoPlayer, so I had to resort to using AVPlayerLayer which does not have the controls.
class PlayerView: UIView {
// Override the property to make AVPlayerLayer the view's backing layer.
override static var layerClass: AnyClass { AVPlayerLayer.self }
// The associated player object.
var player: AVPlayer? {
get { playerLayer.player }
set { playerLayer.player = newValue }
}
private var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
}
struct CustomVideoPlayer: UIViewRepresentable {
let player: AVQueuePlayer
func makeUIView(context: Context) -> PlayerView {
let view = PlayerView()
view.player = player
return view
}
func updateUIView(_ uiView: PlayerView, context: Context) { }
}
Usage:
CustomVideoPlayer(player: player)
.onAppear() {
player.play()
}
.onDisappear() {
player.pause()
}
.ignoresSafeArea(.all)
Another great option is to use a video layer instead of an AVPlayerViewController. This is especially useful if the video needs to repeat, for example, since that seems to be the only way to support that use-case.
Since you're just displaying a video layer, there are no controls by default.
How to loop video with AVPlayerLooper
import Foundation
import SwiftUI
import AVKit
import AVFoundation
struct GifyVideoView : View {
let videoName: String
var body: some View {
if let url = Bundle.main.url(forResource: videoName, withExtension: "mp4") {
GifyVideoViewWrapped(url: url)
} else {
Text("No video for some reason")
}
}
}
fileprivate struct GifyVideoViewWrapped: View {
let player : AVPlayer
var pub = NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime)
init(url: URL) {
player = AVPlayer(url: url)
}
var body: some View {
AVPlayerControllerRepresented(player: player)
.onAppear {
player.play()
}
.onReceive(pub) { (output) in
player.play()
}
}
}
fileprivate struct AVPlayerControllerRepresented : NSViewRepresentable {
var player : AVPlayer
func makeNSView(context: Context) -> AVPlayerView {
let view = AVPlayerView()
view.controlsStyle = .none
view.player = player
return view
}
func updateNSView(_ nsView: AVPlayerView, context: Context) { }
}
usage:
GifyVideoView(VideoName: "someMp4VideoName")
.frame(width: 314, height: 196 * 2)
.clipShape( RoundedRectangle(cornerRadius: 15, style: .continuous) )
video will be played looped ant without controls.
Video will be taken from project resources

Remote Transport Control does not show up for radio stream SwiftUI app with AVPlayer

I am developing simple app in SwiftUI for one internet radio. It uses AVPlayer for play the stream available at given url. And that works perfectly. I have also set up AVSession in AppDelegate, so the app plays in background, stops playing while the call is incoming and resumes playing after the call. This all works fine. However, I wasn't able neither to bring the remote control on lock screen nor showing app in Player tile in Control Center.
The app is written using SwiftUI, I am also moving from traditional completion blocks and targets into Combine. I have created separate class Player, which is ObservableObject (and observed by ContentView), where I set up AVPlayer, AVPlayerItem (with given URL for stream). And all works fine. App updates the state on change of player state. I am not using AVPlayerViewController, since I don't need one. On initialization of that Player object I am also setting up Remote Transport Controls using this method (I moved from setting targets to publishers).
func setupRemoteTransportControls() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.publisher(for: \.playCommand)
.sink(receiveValue: {_ in self.play() })
.store(in: &cancellables)
commandCenter.publisher(for: \.stopCommand)
.sink(receiveValue: {_ in self.stop() })
.store(in: &cancellables)
}
Either I use the original version of that method provided by Apple, or my own version (as shown above) the Remote Control doesn't show up, and the Control Center tile player is not updated.
Of course I use the method provided by Apple for updating NowPlaying
func setupNowPlaying() {
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "Radio"
if let image = UIImage(systemName: "radio") {
nowPlayingInfo[MPMediaItemPropertyArtwork] =
MPMediaItemArtwork(boundsSize: image.size) { size in
return image
}
}
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds ?? ""
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player?.currentItem?.asset.duration.seconds ?? ""
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1 : 0
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
I don't know where is the problem. Is it the way I set up Remote Transport Controls? The flow is like this:
Observable Player object with AVPlayer and setup for Remote Transport Controls and NowPlaying -> observed by -> Content View.
Here is full listing for Player class:
import Foundation
import AVKit
import Combine
import MediaPlayer
class Player: ObservableObject {
private let streamURL = URL(string: "https://stream.rcs.revma.com/ypqt40u0x1zuv")!
#Published var status: Player.Status = .stopped
#Published var isPlaying = false
#Published var showError = false
#Published var isMuted = false
var player: AVPlayer?
var cancellables = Set<AnyCancellable>()
init() {
setupRemoteTransportControls()
}
func setupPlayer() {
let item = AVPlayerItem(url: streamURL)
player = AVPlayer(playerItem: item)
player?.allowsExternalPlayback = true
}
func play() {
handleInterruption()
handleRouteChange()
setupPlayer()
player?.play()
player?.currentItem?.publisher(for: \.status)
.sink(receiveValue: { status in
self.handle(status: status)
})
.store(in: &cancellables)
}
func stop() {
player?.pause()
player = nil
status = .stopped
}
func mute() {
player?.isMuted.toggle()
isMuted.toggle()
}
func handle(status: AVPlayerItem.Status) {
switch status {
case .unknown:
self.status = .waiting
self.isPlaying = false
case .readyToPlay:
self.status = .ready
self.isPlaying = true
self.setupNowPlaying()
case .failed:
self.status = .failed
self.isPlaying = false
self.showError = true
self.setupNowPlaying()
default:
self.status = .stopped
self.isPlaying = false
self.setupNowPlaying()
}
}
func handleInterruption() {
NotificationCenter.default.publisher(for: AVAudioSession.interruptionNotification)
.map(\.userInfo)
.compactMap {
$0?[AVAudioSessionInterruptionTypeKey] as? UInt
}
.map { AVAudioSession.InterruptionType(rawValue: $0)}
.sink { (interruptionType) in
self.handle(interruptionType: interruptionType)
}
.store(in: &cancellables)
}
func handle(interruptionType: AVAudioSession.InterruptionType?) {
switch interruptionType {
case .began:
self.stop()
case .ended:
self.play()
default:
break
}
}
typealias UInfo = [AnyHashable: Any]
func handleRouteChange() {
NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification)
.map(\.userInfo)
.compactMap({ (userInfo) -> (UInfo?, UInt?) in
(userInfo, userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt)
})
.compactMap({ (result) -> (UInfo?, AVAudioSession.RouteChangeReason?) in
(result.0, AVAudioSession.RouteChangeReason(rawValue: result.1 ?? 0))
})
.sink(receiveValue: { (result) in
self.handle(reason: result.1, userInfo: result.0)
})
.store(in: &cancellables)
}
func handle(reason: AVAudioSession.RouteChangeReason?, userInfo: UInfo?) {
switch reason {
case .newDeviceAvailable:
let session = AVAudioSession.sharedInstance()
for output in session.currentRoute.outputs where output.portType == AVAudioSession.Port.headphones {
DispatchQueue.main.async {
self.play()
}
}
case .oldDeviceUnavailable:
if let previousRoute = userInfo?[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
for output in previousRoute.outputs where output.portType == AVAudioSession.Port.headphones {
DispatchQueue.main.sync {
self.stop()
}
break
}
}
default:
break
}
}
}
extension Player {
enum Status {
case waiting, ready, failed, stopped
}
}
extension Player {
func setupRemoteTransportControls() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.publisher(for: \.playCommand)
.sink(receiveValue: {_ in self.play() })
.store(in: &cancellables)
commandCenter.publisher(for: \.stopCommand)
.sink(receiveValue: {_ in self.stop() })
.store(in: &cancellables)
}
func setupNowPlaying() {
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "Radio"
if let image = UIImage(systemName: "radio") {
nowPlayingInfo[MPMediaItemPropertyArtwork] =
MPMediaItemArtwork(boundsSize: image.size) { size in
return image
}
}
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentItem?.currentTime().seconds ?? ""
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player?.currentItem?.asset.duration.seconds ?? ""
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1 : 0
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
}
It appeared that I need to add in AppDelegate in application(:didFinishLaunchWithOptions) one line of code:
UIApplication.shared.beginReceivingRemoteControlEvents()
That solved the problem. Now remote controller is visible on lock screen and it also works in Control Center.
Once additional fix. Changing targets to publisher in setupRemoteTransportControls() in my Player object didn't work. So I switched back to setting targets like this.
func setupRemoteTransportControls() {
let commandCenter = MPRemoteCommandCenter.shared()
// Add handler for Play Command
commandCenter.playCommand.addTarget { event in
self.play()
return .success
}
// Add handler for Pause Command
commandCenter.pauseCommand.addTarget { event in
self.stop()
return .success
}
}

Avplayer AVPlayerItemDidPlayToEndTime notificaton not sending message to observer

I am using AVQueuePlayer to play a list of remote audio files. I want to implement repeat all by default.
My approach, I am observing AVPlayerItemDidPlayToEndTime notification and I add the playerItem to the back of the Queue when it has finished playing.
The nextAudio(notification: Notification) is not running at all. Need help on this or better still a better way to implement infinite play.
func playAudio(_ items: [AVPlayerItem]) {
let avPlayerVC = AVPlayerViewController()
let player = AVQueuePlayer(items: items)
player.actionAtItemEnd = .pause
avPlayerVC.player = player
for item in items {
NotificationCenter.default.addObserver(self,
selector: #selector(FilesViewController.nextAudio(notification:)),
name: .AVPlayerItemDidPlayToEndTime, object: item)
}
present(avPlayerVC, animated: true) {
self.player.play()
}
}
#objc func nextAudio(notification: Notification) {
debugPrint("nextAudio was called")
guard player != nil else { return }
debugPrint("AVPlayerItemDidPlayToEndTime notif info \(notification.userInfo)")
if let currentItem = notification.userInfo!["object"] as? AVPlayerItem {
currentItem.seek(to: kCMTimeZero)
self.player.advanceToNextItem()
self.player.insert(currentItem, after: nil)
}
}
I am sure you already figured it out but I just encountered myself and decided to answer anyways:
It looks like it is not delivered when you specify an object of notification (which should be a proper way BTW). may be a bug in iOS...
You need to pass nil instead:
NotificationCenter.default.addObserver(self, selector: #selector(videoDidPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)
Then in the method you should check in the notification's object is your player item. Documentation is actually not consistent because Apple states that notification's object is a AVplayer but it is a AVPlayerItem:
#objc
private func videoDidPlayToEnd(_ notification: Notification) {
guard let playerItem = notification.object as? AVPlayerItem, let urlAsset = playerItem.asset as? AVURLAsset else { return }
gpLog("Sender urlAsset: \(urlAsset.url.absoluteString)")
// Compare an asset URL.
}
Thank you #Lukasz for your great answer! I was having an issue with multiple videos firing the notification at the wrong time. Your answer helped me fix it.
If anyone is looking for examples of how to use this with SwiftUI here's my code below:
First create a player:
import SwiftUI
import AVKit
struct VideoPlayer : UIViewControllerRepresentable {
func makeCoordinator() -> VideoPlayer.Coordinator {
return VideoPlayer.Coordinator(parent1: self)
}
#Binding var didFinishVideo : Bool
#Binding var player : AVPlayer
var play: Bool
var loop: Bool
var videoName: String
var controller = AVPlayerViewController()
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPlayer>) -> AVPlayerViewController {
controller.player = player
controller.showsPlaybackControls = false
controller.videoGravity = .resize
NotificationCenter.default.addObserver(context.coordinator, selector: #selector(context.coordinator.playerDidFinishPlaying(_:)), name: .AVPlayerItemDidPlayToEndTime, object: nil)
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayer>) {
if play {
player.play()
}
}
class Coordinator : NSObject{
var parent : VideoPlayer
init(parent1 : VideoPlayer) {
parent = parent1
}
#objc func playerDidFinishPlaying(_ notification: Notification) {
guard let playerItem = notification.object as? AVPlayerItem, let urlAsset = playerItem.asset as? AVURLAsset else { return }
print("Sender urlAsset: \(urlAsset.url.absoluteString)")
if urlAsset.url.absoluteString.contains(parent.videoName) {
if parent.loop {
parent.player.seek(to: CMTime.zero)
parent.player.play()
} else {
parent.didFinishVideo = true
}
}
}
}
}
The you can use it to create multiple videos like this:
import SwiftUI
import AVKit
struct ExampleVideo: View {
#Binding var didFinishVideo : Bool
var play: Bool
#State private var player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "exampleVideoFileName", ofType: "mp4")!))
var body: some View {
VideoPlayer(didFinishVideo: $didFinishVideo, player: $player, play: play, loop: false, videoName: "exampleVideoFileName")
}
}
struct ExampleVideo_Previews: PreviewProvider {
static var previews: some View {
CeveraIntroVideo(didFinishVideo: .constant(true), play: true)
}
}
Here's an example of using it in a view after something loads:
struct IntroScreens: View {
#State var loadingComplete = false
#State var didFinishVideo = false
var body: some View {
ZStack{
ExampleVideo(didFinishVideo: $didFinishVideo, play: loadingComplete)
.zIndex(loadingComplete ? 3 : 0)
.animation(Animation.easeInOut(duration: 1))
}
}
}

audioPlayerDidFinishPlaying func issues

I am trying to make my app so that when a user touches a UIImageView A certain sound will play. However, while that sound is playing, I want the UIImageView.isUserInteractionEnabled to be assigned to false. Once the sound is done playing I want the UIImageView.isUserInteractionEnabled to be assigned to true. When ever I run the following code I get an error in the card class, even if I force unwrap. Below is the class that contains the image view I want to disable.
class SecondViewController: UIViewController , UIGestureRecognizerDelegate {
#IBOutlet weak var imgPhoto: UIImageView!
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
imgPhoto.isUserInteractionEnabled = false
itemList[imageIndex].playSound()
}
}
This is the class where the playSound func is located.
import Foundation; import UIKit; import AVFoundation
class Card: NSObject
{
var player: AVAudioPlayer?
var svc = SecondViewController()
var image: UIImage
var soundUrl: String
init(image: UIImage, soundUrl: String, isActive:Bool = true) {
self.image = image
self.soundUrl = soundUrl
}
func playSound()
{
guard let url = Bundle.main.url(forResource: self.soundUrl, withExtension: "m4a") else { return }
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
player?.delegate = self
guard let player = player else { return }
player.prepareToPlay()
player.play()
audioPlayerDidFinishPlaying(player)
print("play")
} catch let error {
print(error.localizedDescription)
}
}
}
extension Card: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer){
svc.imgPhoto.isUserInteractionEnabled = true
}
}
this is the error I get
The problem is this line:
var svc = SecondViewController()
That svc is not your SecondViewController, the existing one in the interface with an imgPhoto. Instead, you are creating a new, separate, and above all blank view controller with an empty view, so its imgPhoto is nil and you crash when you refer to it.
What you want to do is use the "delegate" pattern to hold a reference to the real SecondViewController so you can talk to it.

Resources