My code for the alert is as follows:
struct AlertControlView: UIViewControllerRepresentable {
#Binding var textString: String
#Binding var showAlert: Bool
var title: String
var message: String
// Make sure that, this fuction returns UIViewController, instead of UIAlertController.
// Because UIAlertController gets presented on UIViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertControlView>) -> UIViewController {
return UIViewController() // Container on which UIAlertContoller presents
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<AlertControlView>) {
// Make sure that Alert instance exist after View's body get re-rendered
guard context.coordinator.alert == nil else { return }
if self.showAlert {
// Create UIAlertController instance that is gonna present on UIViewController
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
context.coordinator.alert = alert
// Adds UITextField & make sure that coordinator is delegate to UITextField.
alert.addTextField { textField in
textField.placeholder = "Enter some text"
textField.text = self.textString // setting initial value
textField.delegate = context.coordinator // using coordinator as delegate
}
// As usual adding actions
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "") , style: .destructive) { _ in
// On dismiss, SiwftUI view's two-way binding variable must be update (setting false) means, remove Alert's View from UI
alert.dismiss(animated: true) {
self.showAlert = false
}
})
alert.addAction(UIAlertAction(title: NSLocalizedString("Submit", comment: ""), style: .default) { _ in
// On submit action, get texts from TextField & set it on SwiftUI View's two-way binding varaible `textString` so that View receives enter response.
if let textField = alert.textFields?.first, let text = textField.text {
self.textString = text
}
alert.dismiss(animated: true) {
self.showAlert = false
}
})
// Most important, must be dispatched on Main thread,
// Curious? then remove `DispatchQueue.main.async` & find out yourself, Dont be lazy
DispatchQueue.main.async { // must be async !!
uiViewController.present(alert, animated: true, completion: {
self.showAlert = false // hide holder after alert dismiss
context.coordinator.alert = nil
})
}
}
}
func makeCoordinator() -> AlertControlView.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
// Holds reference of UIAlertController, so that when `body` of view gets re-rendered so that Alert should not disappear
var alert: UIAlertController?
// Holds back reference to SwiftUI's View
var control: AlertControlView
init(_ control: AlertControlView) {
self.control = control
}
func textFieldDidEndEditing(_ textField: UITextField) {
textField.text = textField.text!.replacingOccurrences(of: "^0+", with: "")
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as NSString? {
self.control.textString = text.replacingCharacters(in: range, with: string)
} else {
self.control.textString = ""
}
return true
}
}
}
Its being called as such:
#State var changePassword = false
var body: some View {
ZStack {
HStack {
Text("Password")
Spacer()
Text(String(editingPool.password))
.foregroundColor(.gray)
.font(.callout)
Button(action: {
changePassword.toggle()
}) {
Text("Change")
}
}
if self.changePassword {
AlertControlView(textString: $editingPool.password,
showAlert: $changePassword,
title: "Change Pool Password",
message: "Enter the new pool password.")
}
}
}
I cant seem to get the shouldChangeCharactersIn function to get called, or any of the begin, end or onchange functions either?
Related
I'm building a SwiftUI app using MVVM.
I need some additional behaviors for text field, so I'm wrapping a UITextField in a UIViewRepresentable view.
If I use a simple #State in the view that contains my text fields to bind the text, the custom text field behave as expected; but since I want to store all texts of my text fields in the view model, I'm using an #ObservedObject; when using that, the text field binding doesn't work:
it looks like it's always reset to initial state (empty text) and it doesn't publish any value (and the view doesn't refresh).
This weird behavior happens only for UIViewRepresentable views.
My main view contains a form and it looks like this:
struct LoginSceneView: View {
#ObservedObject private var viewModel: LoginViewModel = LoginViewModel()
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 22) {
UIKitTextField(text: $viewModel.email, isFirstResponder: $viewModel.isFirstResponder)
SecureField("Password", text: $viewModel.password)
Button(action: {}) {
Text("LOGIN")
}
.disabled(!viewModel.isButtonEnabled)
}
.padding(.vertical, 40)
}
}
}
The view model is this:
class LoginViewModel: ObservableObject {
#Published var email = ""
#Published var password = ""
#Published var isFirstResponder = false
var isButtonEnabled: Bool { !email.isEmpty && !password.isEmpty }
}
And finally, this is my custom text field:
struct UIKitTextField: UIViewRepresentable {
// MARK: - Coordinator
class Coordinator: NSObject, UITextFieldDelegate {
private let textField: UIKitTextField
fileprivate init(_ textField: UIKitTextField) {
self.textField = textField
super.init()
}
#objc fileprivate func editingChanged(_ sender: UITextField) {
let text = sender.text ?? ""
textField.text = text
textField.onEditingChanged(text)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.textField.onEditingBegin()
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.textField.onEditingEnd()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.textField.onReturnKeyPressed()
}
}
// MARK: - Properties
#Binding private var text: String
private let onEditingChanged: (String) -> Void
private let onEditingBegin: () -> Void
private let onEditingEnd: () -> Void
private let onReturnKeyPressed: () -> Bool
// MARK: - Initializers
init(text: Binding<String>,
onEditingChanged: #escaping (String) -> Void = { _ in },
onEditingBegin: #escaping () -> Void = {},
onEditingEnd: #escaping () -> Void = {},
onReturnKeyPressed: #escaping () -> Bool = { true }) {
_text = text
self.onEditingChanged = onEditingChanged
self.onEditingBegin = onEditingBegin
self.onEditingEnd = onEditingEnd
self.onReturnKeyPressed = onReturnKeyPressed
}
// MARK: - UIViewRepresentable methods
func makeCoordinator() -> Coordinator { Coordinator(self) }
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.addTarget(context.coordinator, action: #selector(Coordinator.editingChanged(_:)), for: .editingChanged)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
}
}
Your problem may be that UIViewRepresentable is created every time that some binding var is changed. Put debug code to check.
struct UIKitTextField: UIViewRepresentable {
init() {
print("UIViewRepresentable init()")
}
...
}
I've edited your code, see if that's what you are looking for.
class LoginViewModel: ObservableObject {
#Published var email = ""
#Published var password = ""
var isButtonEnabled: Bool { !email.isEmpty && !password.isEmpty }
}
struct LoginSceneView: View {
#ObservedObject private var viewModel: LoginViewModel = LoginViewModel()
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 22) {
UIKitTextField(text: $viewModel.email)
SecureField("Password", text: $viewModel.password)
Button(action: {
print("email is \(self.viewModel.email)")
print("password is \(self.viewModel.password)")
UIApplication.shared.endEditing()
}) {
Text("LOGIN")
}
.disabled(!viewModel.isButtonEnabled)
}
.padding(.vertical, 40)
}
}
}
struct UIKitTextField: UIViewRepresentable {
// MARK: - Coordinator
class Coordinator: NSObject, UITextFieldDelegate {
let textField: UIKitTextField
fileprivate init(_ textField: UIKitTextField) {
self.textField = textField
super.init()
}
#objc fileprivate func editingChanged(_ sender: UITextField) {
let text = sender.text ?? ""
textField.text = text
textField.onEditingChanged(text)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.textField.onEditingBegin()
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.textField.onEditingEnd()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.textField.onReturnKeyPressed()
}
}
// MARK: - Properties
#Binding private var text: String
private let onEditingChanged: (String) -> Void
private let onEditingBegin: () -> Void
private let onEditingEnd: () -> Void
private let onReturnKeyPressed: () -> Bool
// MARK: - Initializers
init(text: Binding<String>,
onEditingChanged: #escaping (String) -> Void = { _ in },
onEditingBegin: #escaping () -> Void = {},
onEditingEnd: #escaping () -> Void = {},
onReturnKeyPressed: #escaping () -> Bool = { true }) {
_text = text
self.onEditingChanged = onEditingChanged
self.onEditingBegin = onEditingBegin
self.onEditingEnd = onEditingEnd
self.onReturnKeyPressed = onReturnKeyPressed
}
// MARK: - UIViewRepresentable methods
func makeCoordinator() -> Coordinator { Coordinator(self) }
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.addTarget(context.coordinator, action: #selector(Coordinator.editingChanged(_:)), for: .editingChanged)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
}
}
I highly recommend not making your UIViewRepresentable complex for features like using of isFirstResponder to do what's possible with alternative ways unless necessary. I'm assuming you want to use that as a parameter to dismiss the keyboard. There are some alternatives like :
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
I am creating a email input and a name input, and I need to show a message when all fields are complete. But for some reason, when empty fields are there and the user clicks on the submit button, the success message shows instead. Is there way to put in a clause or something to fire it after all fields are completed? thanks for the help.
Here is my code:
import UIKit
import MessageUI
class RequestFilterVC: UIViewController {
#IBOutlet weak var emailTxtField: UITextField!
#IBOutlet weak var filterTxtField: UITextField!
#IBOutlet weak var requestBtn: UIButton!
#IBOutlet weak var validatorMessage: UILabel!
#IBOutlet weak var requestedMessage: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// hide validator message
validatorMessage.isHidden = true
requestedMessage.isHidden = true
}
#IBAction func requestBtnWasTapped(_ sender: Any) {
let providedEmailAddress = emailTxtField.text
let isEmailAddressValid = isValidEmailAddress(emailAddressString: providedEmailAddress!)
if isEmailAddressValid
{
print("Email address is valid")
} else {
print("Email address is not valid")
displayAlertMessage(messageToDisplay: "Email address is not valid")
}
// If All are completed then send the email .
let composeVC = MFMailComposeViewController()
composeVC.mailComposeDelegate = self as? MFMailComposeViewControllerDelegate
// Configure the fields of the interface.
composeVC.setToRecipients(["myemail#awesomeemail.com])
composeVC.setSubject("Form Submit)
composeVC.setMessageBody("\(emailTxtField, filterTxtField)", isHTML: false)
// Present the view controller modally
// self.present(composeVC, animated: true, completion: nil)
requestedMessage.isHidden = false
requestedMessage.text = "Submitted form . thank you"
}
func isValidEmailAddress(emailAddressString: String) -> Bool {
var returnValue = true
let emailRegEx = "[A-Z0-9a-z.-_]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,3}"
do {
let regex = try NSRegularExpression(pattern: emailRegEx)
let nsString = emailAddressString as NSString
let results = regex.matches(in: emailAddressString, range: NSRange(location: 0, length: nsString.length))
if results.count == 0
{
returnValue = false
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
returnValue = false
}
return returnValue
}
func displayAlertMessage(messageToDisplay: String)
{
let alertController = UIAlertController(title: "Error", message: messageToDisplay, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) { (action:UIAlertAction!) in
print("Ok button tapped");
}
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion:nil)
}
}
You are not checking if the second text field is empty or not anywhere. ( I have no clue why you have named a text field that is supposed to store a name is named as filterTextField )
if isEmailAddressValid && !filterTextField.text?.isEmpty {
print("Email address is valid")
} else {
print("Email address is not valid")
displayAlertMessage(messageToDisplay: "Email address is not valid")
return
}
How does it sound create a delegate with a regex? each time you press a key call the delegate and if it matches the regex you could unhide a UILabel... something like this could work.
Change
displayAlertMessage(messageToDisplay: "Email address is not valid")
To
displayAlertMessage(messageToDisplay: "Email address is not valid")
return
For your problem the following change will handle the error
After you validate the email you are not returning or braking the flow and it will execute the remaining coed left in your function. So you need to stop the execution.
if isEmailAddressValid
{
print("Email address is valid")
} else {
print("Email address is not valid")
displayAlertMessage(messageToDisplay: "Email address is not valid")
}
How ever for better approach, I would suggest to disable the submit
button until the text field has value.
func textFieldDidBeginEditing(textField: UITextField!) { //delegate method
//check for the required text field
if (emailTxtField.text!.isEmpty){
//disable submit button
}
else{
// enable the submit button
}
}
func textFieldShouldEndEditing(textField: UITextField!) -> Bool { //delegate method
if (emailTxtField.text!.isEmpty){
//disable submit button
}
else{
// enable the submit button
}
return true
}
I am presenting an alert with UITextfield, but its delegate methods are not getting called. What wrong I might be doing. I am using the below code to show the alert with textfield.
func takePasscodeToEnableTouch(){
self.passcodeInputOperationType = .EnableTouchID
alertControllerPassCodeEntry = UIAlertController(title: "", message: "Enter Passcode to enable the Touch Id.", preferredStyle: UIAlertControllerStyle.Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (action) -> Void in
}
alertControllerPassCodeEntry!.addAction(cancelAction)
alertControllerPassCodeEntry!.addTextFieldWithConfigurationHandler { (txtField) -> Void in
txtField.placeholder = "Enter passcode"
txtField.delegate = self
txtField.tag = TextFieldTag.EnterPassCode
txtField.keyboardType = UIKeyboardType.NumbersAndPunctuation
txtField.accessibilityIdentifier = "PassCode"
txtField.secureTextEntry = true
txtField.addTarget(self, action:"textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController?.presentViewController(alertControllerPassCodeEntry!, animated: true, completion: nil )
}
And the textField delegate methods are :
func textFieldShouldBeginEditing(textField: UITextField) -> Bool
{
return true
}
func textFieldDidBeginEditing(textField: UITextField) // became first responder
{
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool
{
return true
}
func textFieldDidEndEditing(textField: UITextField)
{
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
var isLimitExist: Bool
var accessIndentifier: String
if let str = textField.accessibilityIdentifier
{
accessIndentifier = str
}
else
{
accessIndentifier = ""
}
//checkFieldLimit function is used to check the limit of text and restrict
isLimitExist = UIUtils.checkFieldLimit(accessIndentifier, stringToMatch: textField.text!, rangeLength: range.length, stringLength: string.characters.count)
if !isLimitExist
{
return false
}
return true
}
Ok, so with information from the comments, everything seems clear now. To recap, you call the method showing the alert like this :
#IBAction func swtchTouchAction(sender: UISwitch) {
if sender.on {
let passCodeManager = PasscodeManager()
passCodeManager.delegate = self
passCodeManager.takePasscodeToEnableTouch()
} else {
let passCodeManager = PasscodeManager()
passCodeManager.delegate = self
passCodeManager.authenticatePasscodeToDisalbeTouch()
}
}
Now, you don't retain (meaning - assign to a strong property) the passCodeManager anywhere in here. This means, that at the end of this method this object gets destroyed (thanks to ARC - Automatic Reference Counting). One may think that it would get retained because you assigned it as a delegate of the text field, but delegates are weak proeprties 99.99% of time - this means that they don't bump the retain count of objects assigned to them.
To solve your immediate issue you should make a property in your class in which you have swtchTouchAction method and change your code like this :
var passCodeManager: PasscodeManager?
#IBAction func swtchTouchAction(sender: UISwitch) {
if sender.on {
self.passCodeManager = PasscodeManager()
self.passCodeManager?.delegate = self
self.passCodeManager?.takePasscodeToEnableTouch()
} else {
self.passCodeManager = PasscodeManager()
self.passCodeManager?.delegate = self
self.passCodeManager?.authenticatePasscodeToDisalbeTouch()
}
}
This will be enough to retain your passcode manager.
I'd also suggest you read up on how memory management is done in Swift.
I am working on an assignment for a job interview. I have finished most of the assignment. There's only a bug i can't figure out. I have been trying for three days now.
I had to make a client app for the Flickr API that allows users to search for photos using specific words. Display the results in a collection view with infinite scroll. And when a photo is selected it should show the details of the photo in a detail view.
The bug:
Everything is working if i stay in the collection view. I can search over and over again and the infinite scroll is also working.As soon as a specific index in the index path is hit. A new request is sent with the same search term. But if i select a photo and then navigate back to the collection view and try a new search nothing comes back and my error handeling returns an error. (the error is not a console error).Also when navigating back from detail to collection. I can still scroll until the index triggers a new request than it also throws an error.
I hope i am explaining it well. I am really getting desperate at the moment. I tried everything i could think of: the request url still works when i try it in the browser.
Please help! If you need more info just ask.
Collection view controller:
import UIKit
// Global variable for holding a search term.
var searchTerm: String?
// Global variable to hold an instance of Reachability.
var reachability: Reachability?
// Enum for changing the textfield placeholder text.
enum TextFieldPlaceHolderText: String {
case Search = "Search"
case Searching = "Searching..."
}
class PhotosViewController: UIViewController {
// MARK: - Outlets
#IBOutlet var collectionView: UICollectionView!
#IBOutlet var searchTextField: UITextField!
// MARK: - Properties
let photoDataSource = PhotoDataSource()
let photoStore = PhotoStore()
// MARK: - View Setup
override func viewDidLoad() {
super.viewDidLoad()
// Sets the data source and delegate.
collectionView.dataSource = photoDataSource
collectionView.delegate = self
// Uses an image to add a pattern to the collection view background.
collectionView.backgroundColor = UIColor(patternImage: UIImage(named: "flickr.png")!)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Checks if the device is connected to the internet.
checkForReachability()
}
// MARK: showAlert
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "Ok", style: .Cancel, handler: { (nil) in
self.dismissViewControllerAnimated(true, completion: nil)
})
alert.addAction(okAction)
self.presentViewController(alert, animated: true, completion: nil)
}
// MARK: - Segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowPhoto" {
if let selectedIndexPath = collectionView.indexPathsForSelectedItems()?.first {
let flickrPhoto = photoDataSource.flickrPhotos[selectedIndexPath.row]
let destinationVC = segue.destinationViewController as! PhotoDetailViewController
destinationVC.flickrPhoto = flickrPhoto
destinationVC.photoStore = photoStore
}
}
}
// MARK: - checkForReachability
func checkForReachability() {
do {
reachability = try Reachability.reachabilityForInternetConnection()
} catch {
print("Unable to create Reachability")
return
}
reachability!.whenReachable = { reachability in
// This is called on a background thread, but UI updates must be on the main thread.
NSOperationQueue.mainQueue().addOperationWithBlock({
if reachability.isReachableViaWiFi() {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
})
}
reachability!.whenUnreachable = { reachability in
// This is called on a background thread, but UI updates must be on the main thread.
NSOperationQueue.mainQueue().addOperationWithBlock({
print("Not reachable")
self.showAlert("No Internet Connection", message: "Make sure your device is connected to the internet.")
})
}
do {
try reachability!.startNotifier()
} catch {
print("Unable to start notifier")
}
}
}
//MARK: - Extension UICollectionViewDelegate
extension PhotosViewController: UICollectionViewDelegate {
//MARK: - willDisplayCell
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
let flickrPhoto = photoDataSource.flickrPhotos[indexPath.row]
// Downloads the image data for a thumbnail.
photoStore.fetchImageForPhoto(flickrPhoto,thumbnail: true) { (result) -> Void in
// Calls the mainthread to update the UI.
NSOperationQueue.mainQueue().addOperationWithBlock() {
// The indexpath for the photo might have changed between the time the request started and finished, so find the most recent indeaxpath
let photoIndex = self.photoDataSource.flickrPhotos.indexOf(flickrPhoto)!
let photoIndexPath = NSIndexPath(forRow: photoIndex, inSection: 0)
// When the request finishes, only update the cell if it's still visible
if let cell = collectionView.cellForItemAtIndexPath(photoIndexPath) as? PhotoCollectionViewCell {
cell.updateWithImage(flickrPhoto.image)
}
}
}
}
}
//MARK: - Extension UITextFieldDelegate
extension PhotosViewController : UITextFieldDelegate {
func textFieldShouldReturn(textField: UITextField) -> Bool {
// Checks if the textfield is not empty.
if textField.text!.isEmpty {
self.showAlert("S😉rry", message: "No search term detected, please enter a search term.")
return false
}
else {
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
textField.addSubview(activityIndicator)
activityIndicator.frame = textField.bounds
activityIndicator.startAnimating()
textField.placeholder = TextFieldPlaceHolderText.Searching.rawValue
// Sets the text that the user typed as the value for the searchTerm property.
searchTerm = textField.text!
// Fetches the photos from flickr using the user's search term.
photoStore.fetchPhotosForSearchTerm() {
(photosResult) -> Void in
// Calls the mainthread to update the UI.
NSOperationQueue.mainQueue().addOperationWithBlock() {
switch photosResult {
case let .Success(photos):
// Checks if photos were found using the search term.
if photos.count == 0 {
self.showAlert("S😞rry", message: "No images found matching your search for: \(searchTerm!), please try again.")
}
activityIndicator.removeFromSuperview()
textField.placeholder = TextFieldPlaceHolderText.Search.rawValue
// Sets the result to the data source array.
self.photoDataSource.flickrPhotos = photos
print("Successfully found \(photos.count) recent photos.")
case let .Failure(error):
self.checkForReachability()
activityIndicator.removeFromSuperview()
textField.placeholder = TextFieldPlaceHolderText.Search.rawValue
self.photoDataSource.flickrPhotos.removeAll()
self.showAlert("", message: "Something went wrong, please try again.")
print("Error fetching photo's for search term: \(searchTerm!), error: \(error)")
}
self.collectionView.reloadSections(NSIndexSet(index: 0))
}
}
textField.text = nil
textField.resignFirstResponder()
self.collectionView?.backgroundColor = UIColor.whiteColor()
return true
}
}
}
The detail view controller:
import UIKit
import Social
class PhotoDetailViewController: UIViewController {
// MARK: - Outlets
#IBOutlet var photoTitleLabel: UILabel!
#IBOutlet var photoIDLabel: UILabel!
#IBOutlet var dateTakenLabel: UILabel!
#IBOutlet var imageView: UIImageView!
// MARK: Properties
var flickrPhoto: FlickrPhoto!
var photoStore: PhotoStore!
let formatter = FlickrAPI.dateFormatter
// MARK: - View Setup
override func viewDidLoad() {
super.viewDidLoad()
//Downloads the image data for large image
photoStore.fetchImageForPhoto(flickrPhoto, thumbnail: false) { (result) -> Void in
switch result {
case let .Success(image):
// Calls the mainthread to update the UI.
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = image
}
case let .Failure(error):
print(" Error fetching detail image for photo: \(error)")
}
}
// Formats the date a shorte date that doesn't display the time
formatter.dateStyle = .MediumStyle
formatter.timeStyle = .NoStyle
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Checks if the device is connected to the internet.
checkForReachability()
// Configures the UI.
configureView()
}
// MARK: - checkForReachability
func checkForReachability() {
do {
reachability = try Reachability.reachabilityForInternetConnection()
} catch {
print("Unable to create Reachability")
return
}
reachability!.whenReachable = { reachability in
// this is called on a background thread, but UI updates must be on the main thread, like this:
NSOperationQueue.mainQueue().addOperationWithBlock({
if reachability.isReachableViaWiFi() {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
})
}
reachability!.whenUnreachable = { reachability in
// this is called on a background thread, but UI updates must be on the main thread, like this:
NSOperationQueue.mainQueue().addOperationWithBlock({
print("Not reachable")
self.showAlert("No Internet Connection", message: "Make sure your device is connected to the internet.")
})
}
do {
try reachability!.startNotifier()
} catch {
print("Unable to start notifier")
}
}
// MARK: - configureView
func configureView() {
photoTitleLabel.text = flickrPhoto.title ?? "No title available"
photoIDLabel.text = flickrPhoto.photoID ?? "ID unknown"
dateTakenLabel.text = formatter.stringFromDate(flickrPhoto.dateTaken) ?? " Date unknown"
}
// MARK: - showShareOptions
#IBAction func showShareOptions(sender: AnyObject) {
// Configure an action sheet to show the sharing options.
let actionSheet = UIAlertController(title: "Share this photo", message: "", preferredStyle: UIAlertControllerStyle.ActionSheet)
let tweetAction = UIAlertAction(title: "Share on Twitter", style: UIAlertActionStyle.Default) { (action) -> Void in
// Check if sharing to Twitter is possible.
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) {
let twitterComposeVC = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
twitterComposeVC.addImage(self.imageView.image)
self.presentViewController(twitterComposeVC, animated: true, completion: nil)
}
else {
self.showAlert("Flickr Searcher", message: "You are not logged in to your Twitter account.")
}
}
// Configure a new action to share on Facebook.
let facebookPostAction = UIAlertAction(title: "Share on Facebook", style: UIAlertActionStyle.Default) { (action) -> Void in
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) {
let facebookComposeVC = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
facebookComposeVC.addImage(self.imageView.image)
self.presentViewController(facebookComposeVC, animated: true, completion: nil)
}
else {
self.showAlert("Flickr Searcher", message: "You are not logged in to your facebook account.")
}
}
// Configure a new action to show the UIActivityViewController
let moreAction = UIAlertAction(title: "More", style: UIAlertActionStyle.Default) { (action) -> Void in
let activityViewController = UIActivityViewController(activityItems: [self.imageView.image!], applicationActivities: nil)
self.presentViewController(activityViewController, animated: true, completion: nil)
}
let dismissAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Destructive) { (action) -> Void in
}
actionSheet.addAction(tweetAction)
actionSheet.addAction(facebookPostAction)
actionSheet.addAction(moreAction)
actionSheet.addAction(dismissAction)
presentViewController(actionSheet, animated: true, completion: nil)
}
// MARK: showAlert
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "Ok", style: .Cancel, handler: { (nil) in
self.dismissViewControllerAnimated(true, completion: nil)
})
alert.addAction(okAction)
self.presentViewController(alert, animated: true, completion: nil)
}
}
My data source:
import UIKit
class PhotoDataSource: NSObject, UICollectionViewDataSource {
//MARK: - Properties
// Array to store the Flickr Photos
var flickrPhotos = [FlickrPhoto]()
// An instance of photoStore.
var photoStore = PhotoStore()
// MARK: - numberOfItemsInSection
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return flickrPhotos.count
}
// MARK: - cellForItemAtIndexPath
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let identifier = "FlickrCell"
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier, forIndexPath: indexPath) as! PhotoCollectionViewCell
let photo = flickrPhotos[indexPath.item]
cell.updateWithImage(photo.image)
print(indexPath.item)
// If you get close to the end of the collection, fetch more photo's.
if indexPath.item == flickrPhotos.count - 20 {
print("Detected the end of the collection")
// Fetch the next batch of photos.
photoStore.fetchPhotosForSearchTerm() {
(photosResult) -> Void in
// Calls the mainthread to update the UI.
NSOperationQueue.mainQueue().addOperationWithBlock() {
switch photosResult {
case let .Success(photos):
print("Successfully found \(photos.count) recent photos.")
self.flickrPhotos.appendContentsOf(photos)
case let .Failure(error):
self.flickrPhotos.removeAll()
print("Error fetching more photos for search term \(error)")
}
collectionView.reloadSections(NSIndexSet(index: 0))
}
}
}
return cell
}
}
This is the method that throws the error. But only when navigated to the detail view first. Staying in the collection view the method gets call over and over with no problem:
photoStore.fetchPhotosForSearchTerm() {
(photosResult) -> Void in
// Calls the mainthread to update the UI.
NSOperationQueue.mainQueue().addOperationWithBlock() {
switch photosResult {
case let .Success(photos):
print("Successfully found \(photos.count) recent photos.")
self.flickrPhotos.appendContentsOf(photos)
case let .Failure(error):
self.flickrPhotos.removeAll()
print("Error fetching more photos for search term \(error)")
}
collectionView.reloadSections(NSIndexSet(index: 0))
}
}
Im working on an app that uses phone number and SMS verification to login. It all works well except for one small issue. If I logout of one user, then login with another, the previous users data is loaded, however a new user is created but the previous users data is displayed. I have to logout and login the new user again to load their data. Anybody see whats going on?
Login code:
class LoginViewController: UIViewController {
func displayAlert(title: String, message: String) {
var alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (action) -> Void in
}))
self.presentViewController(alert, animated: true, completion: nil)
}
#IBOutlet weak var instructionLabel: UILabel!
#IBOutlet weak var phoneNumberTextField: UITextField!
#IBOutlet weak var sendCodeButton: UIButton!
var phoneNumber: String = ""
override func viewDidLoad() {
super.viewDidLoad()
first()
self.editing = true
}
func first() {
phoneNumber = ""
phoneNumberTextField.placeholder = "555-555-5555"
instructionLabel.text = "Enter your phone number to login or sign up"
sendCodeButton.enabled = true
}
func second() {
phoneNumber = phoneNumberTextField.text!
phoneNumberTextField.text = ""
phoneNumberTextField.placeholder = "1234"
instructionLabel.text = "Enter your 4 digit security code"
sendCodeButton.enabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
phoneNumberTextField.becomeFirstResponder()
}
#IBAction func didTapSendCodeButton() {
let preferredLanguage = NSBundle.mainBundle().preferredLocalizations[0]
let textFieldText = phoneNumberTextField.text ?? ""
if phoneNumber == "" {
if (preferredLanguage == "en" && textFieldText.characters.count != 10) {
displayAlert("Phone Login", message: NSLocalizedString("warningphone", comment: "You must enter a 10 digit US phone number including area code"))
return first()
}
self.editing = false
let params = ["phoneNumber" : textFieldText, "language" : preferredLanguage]
PFCloud.callFunctionInBackground("sendCode", withParameters: params) { response, error in
self.editing = true
if let error = error {
var description = error.description
if description.characters.count == 0 {
description = NSLocalizedString("warningGeneral", comment: "Something went Wrong. Please try again")
} else if let message = error.userInfo["error"] as? String {
description = message
}
self.displayAlert("Login Error", message: description)
return self.first()
}
return self.second()
}
} else {
if textFieldText.characters.count == 4, let code = Int(textFieldText) {
return doLogin(phoneNumber, code: code)
}
displayAlert("Code Entry", message: NSLocalizedString("warningCodeLength", comment: "You must enter the 4 digit code texted to your number"))
}
}
func doLogin(phoneNumber: String, code: Int) {
self.editing = false
let params = ["phoneNumber": phoneNumber, "codeEntry": code] as [NSObject:AnyObject]
PFCloud.callFunctionInBackground("logIn", withParameters: params) { response, error in
if let description = error?.description {
self.editing = true
return self.displayAlert("Login Error", message: description)
}
if let token = response as? String {
PFUser.becomeInBackground(token) { user, error in
if let _ = error{
self.displayAlert("Login Error", message: NSLocalizedString("warningGeneral", comment: "Something happened while logging in. Please try again"))
self.editing = true
return self.first()
}
return self.dismissViewControllerAnimated(true, completion: nil)
}
} else {
self.editing = true
self.displayAlert("Login Error", message: NSLocalizedString("warningGeneral", comment: "Something went wrong. Please try again"))
return self.first()
}
}
}
override func setEditing(editing: Bool, animated: Bool) {
sendCodeButton.enabled = editing
phoneNumberTextField.enabled = editing
if editing {
phoneNumberTextField.becomeFirstResponder()
}
}
}
extension LoginViewController : UITextFieldDelegate {
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.didTapSendCodeButton()
return true
}
}
Found the problem I had to update the label in viewWillAppear