I am trying to make UIAlertController that looks like this:
How can we customize the UIAlertController to get the result something same as this picture ?
What you are trying to do is a popover, for current versions of iOS you can achieve the same effect for both iPad and iPhone.
1.- Start by building your design on Storyboard or a xib. and then reference it.
2.- then present it as a popover.
3.- maybe you will want to implement popoverdelegates to avoid wrong positions when rotating the device.
for example:
private static func presentCustomDialog(parent: UIViewController) -> Bool {
/// Loads your custom from its xib or from Storyboard
if let rateDialog = loadNibForRate() {
rateDialog.modalPresentationStyle = UIModalPresentationStyle.Popover
rateDialog.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
let x = parent.view.center
let sourceRectX : CGFloat
let maximumDim = max(UIScreen.mainScreen().bounds.height, UIScreen.mainScreen().bounds.width)
if maximumDim == 1024 { //iPad
sourceRectX = x.x
}else {
sourceRectX = 0
}
rateDialog.popoverPresentationController?.sourceView = parent.view
rateDialog.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.allZeros
rateDialog.popoverPresentationController?.sourceRect = CGRectMake(sourceRectX, x.y, 0, 0)
rateDialog.popoverPresentationController?.popoverLayoutMargins = UIEdgeInsetsMake(0, 0, 0, 0)
rateDialog.popoverPresentationController?.delegate = parent
rateDialogParent = parent
dispatch_async(dispatch_get_main_queue(), {
parent.presentViewController(rateDialog, animated: true, completion: nil)
})
return true
}
return false
}
Update: to achieve, point 3... on your parent UIViewController.
public class MyParentViewController: UIViewController, UIPopoverPresentationControllerDelegate {
/**
This function guarantees that the CustomDialog is always centered at parent, it locates the Dialog view
*/
public func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) {
let x = popoverPresentationController.presentingViewController.view.center
let newRect = CGRectMake(x.x, x.y, 0, 0)
rect.initialize(newRect)
}
}
I did the custom popup windows as #Hugo posted, after a while i found a library that is done in a very neat and magnificent way which can be used to implement custom popup views with less effort:
here is the link for the library on Github:
https://github.com/m1entus/MZFormSheetPresentationController
It is written in Objective c, as well as there is swift example included in the library's samples.
to include it into swift project you will need to use something called Bridging-header
Related
We have the following app, where user can switch to different "page" (purple, yellow, ... colours) from side menu.
I was wondering, should the "page" be implemented as UIView, or should the "page" be implemented as UIViewController?
The pages shall responsible to
Read/ write from/ to CoreData.
Possible holding a UIPageView, which user can swipe through multiple child pages as shown in https://i.stack.imgur.com/v0oNo.gif
Holding a UICollectionView.
User can drag and move the items in the UICollectionView
User can perform various contextual action (Delete, clone, ...) on the items in UICollectionView.
Can easily port to iPad in the future.
Currently, my implementation of using UIView are as follow.
private func archive() {
if let trashView = self.trashView {
trashView.removeFromSuperview()
self.trashView = nil
}
if self.archiveView != nil {
return
}
let archiveView = ArchiveView.instanceFromNib()
self.view.addSubview(archiveView)
archiveView.translatesAutoresizingMaskIntoConstraints = false
archiveView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
archiveView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
archiveView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
archiveView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.archiveView = archiveView
}
private func trash() {
if let archiveView = self.archiveView {
archiveView.removeFromSuperview()
self.archiveView = nil
}
if self.trashView != nil {
return
}
let trashView = TrashView.instanceFromNib()
self.view.addSubview(trashView)
trashView.translatesAutoresizingMaskIntoConstraints = false
trashView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
trashView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
trashView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
trashView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.trashView = trashView
}
I notice that if I implement the "pages" using UIView, I will lost some capability of UIViewController like
viewDidLoad callback.
viewWillLoad callback.
viewDidLayoutSubviews callback.
...
However, I am not clear whether losing those capabilities will stop me from implementing a proper "page"?
May I know, should I implement those "pages" using UIView, or using UIViewController?
I would do this with UIViewController because of all the UIKit callback reasons you listed in the question already.
I assume that you have a UINavigationController instance that's set as window.rootViewController for your app. You have a reference to this instance using which you can easily switch between different screens.
Example
class SlideMenuViewController: UIViewController {
enum Option {
case archive
case trash
}
var onSelect: ((_ option: Option) -> Void)?
}
class ArchiveViewController: UIViewController {}
class TrashViewController: UIViewController {}
class AppNavigator {
let mainNavigationController: UINavigationController
init(navigationController: UINavigationController) {
self.mainNavigationController = navigationController
}
private lazy var slideMenuVC: SlideMenuViewController = {
let slideMenu = SlideMenuViewController()
slideMenu.onSelect = { [weak self] (option) in
self?.openScreen(for: option)
}
return slideMenu
}()
private lazy var archiveVC: ArchiveViewController = {
return ArchiveViewController()
}()
private lazy var trashVC: TrashViewController = {
return TrashViewController()
}()
func openScreen(for option: SlideMenuViewController.Option) {
let targetVC: UIViewController
switch option {
case .archive: targetVC = archiveVC
case .trash: targetVC = trashVC
}
mainNavigationController.setViewControllers([targetVC], animated: true)
}
}
Perhaps I totally misunderstood your question, but your UIView should only hold logic related to how the view itself will appear to the user. The UIView should not contain any logic related to the other views or to the model.
UIKit assumes that you use the MVC model to implement apps on the Apple platforms. This means that any code related to controlling which view has to appear and which data the view should get from the model, should be written in the ViewController.
In Xcode you have both the UICollectionViewController and the UIPageViewController to implement page swipes and dragging an dropping views.
View controllers can give the control to other view controllers to present views. The view controller also determine the data that should be presented by the view. Please, check out this article about the MVC model.
Kind regards,
MacUserT
I want to copy one UIView to another view without making it archive or unarchive.
Please help me if you have any solution.
I tried with by making an extension of UIView as already available an answer on Stack over flow. But its crashing when I pass the view with pattern Image Background color.
The code related to my comment below:
extension UIView
{
func copyView() -> UIView?
{
return NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(self)) as? UIView
}
}
I've just tried this simple code in a Playground to check that the copy view works and it's not pointing the same view:
let originalView = UIView(frame: CGRectMake(0, 0, 100, 50));
originalView.backgroundColor = UIColor.redColor();
let originalLabel = UILabel(frame: originalView.frame);
originalLabel.text = "Hi";
originalLabel.backgroundColor = UIColor.whiteColor();
originalView.addSubview(originalLabel);
let copyView = originalView.copyView();
let copyLabel = copyView?.subviews[0] as! UILabel;
originalView.backgroundColor = UIColor.blackColor();
originalLabel.text = "Hola";
originalView.backgroundColor; // Returns black
originalLabel.text; // Returns "Hola"
copyView!.backgroundColor; // Returns red
copyLabel.text; // Returns "Hi"
If the extension wouldn't work, both copyView and originalView would have same backgroundColor and the same would happen to the text of the labels. So maybe there is the possibility that the problem is in other part.
Original Post
func copyView(viewforCopy: UIView) -> UIView {
viewforCopy.hidden = false //The copy not works if is hidden, just prevention
let viewCopy = viewforCopy.snapshotViewAfterScreenUpdates(true)
viewforCopy.hidden = true
return viewCopy
}
Updated for Swift 4
func copyView(viewforCopy: UIView) -> UIView {
viewforCopy.isHidden = false //The copy not works if is hidden, just prevention
let viewCopy = viewforCopy.snapshotView(afterScreenUpdates: true)
viewforCopy.isHidden = true
return viewCopy!
}
I use a UISplitViewController with preferredDisplayMode = UISplitViewControllerDisplayModePrimaryOverlay and I was looking for a way to dismiss master view controller. My master contains a table view and I'd like to close it whenever I select a cell. Surprisingly UISplitViewController doesn't seem to offer a method for that (but I do see Apple Mail doing that we you select an email in portrait mode).
I found the following workaround reported here: Hiding the master view controller with UISplitViewController in iOS8 (look at phatmann answer). This works but it also creates a weird animation when it's dismissed, there's an underlying gray outlined view which is not animated together with my master's view. The problem has been reported also here: iOS Swift 2 UISplitViewController opens detail screen on master's place when on iPad/ iPhone 6+
The problem occurs only when I dismiss master with this workaround, not when I tap on the secondary so I guess UISplitViewController is not following the regular dismiss flow when you just call sendAction on the button.
I used the following code to address this problem. There might be a better way to match on the specific view that is causing the issue. That said, this code was in an app approved by Apple in April of this year. What the code does is look for a specific view of a certain type, and if found, then it makes it hidden until the animation is complete. Its somewhat future proof, since if it does't detect the special view, it does nothing. I also added some comments for adopters on where you might want to make changes.
func closePrimaryIfOpen(finalClosure fc: (() -> Void)? = nil) {
guard let
primaryNavController = viewControllers[0] as? MySpecialNavSubclass,
primaryVC = primaryNavController.topViewController as? MySpecialCalss
else { fatalError("NO Special Class?") }
// no "official" way to know if its open or not.
// The view could keep track of didAppear and willDisappear, but those are not reliable
let isOpen = primaryVC.view.frame.origin.x >= -10 // -10 because could be some slight offset when presented
if isOpen {
func findChromeViewInView(theView: UIView) -> UIView? {
var foundChrome = false
var view: UIView! = theView
var popView: UIView!
repeat {
// Mirror may bring in a lot of overhead, could use NSStringFromClass
// Also, don't match on the full class name! For sure Apple won't like that!
//print("View: ", Mirror(reflecting: view).subjectType, " frame: \(view.frame)")
if Mirror(reflecting: view).description.containsString("Popover") { // _UIPopoverView
for v in view.subviews {
//print("SV: ", Mirror(reflecting: v).subjectType, " frame: \(v.frame)")
if Mirror(reflecting: v).description.containsString("Chrome") {
foundChrome = true
popView = v
//popView.hidden = true
break
}
}
if foundChrome { break }
}
view = view.superview
} while view != nil
return popView
}
// Note: leave it as optional - Apple changes things and we don't find the view, things still work!
let chromeView = findChromeViewInView(self.view)
UIView.animateWithDuration(0.250, animations: {
chromeView?.hidden = true
self.preferredDisplayMode = .PrimaryHidden
}, completion: { Bool in
self.preferredDisplayMode = .PrimaryOverlay
chromeView?.hidden = false
if let finalClosure = fc {
finalClosure()
}
//print("SLIDER CLOSED DONE!!!")
} )
}
}
I have multiple custom pop up views that I call (to set it up, animate the pop up and animate the dismiss) but I want to have one variable that I can replace in the multiple functions that can be set by a switch statement with one variable. So I don't have 4+ of every function with the same code but different uiView names. How is that done please?
var myPopupView:NotePopUpView!
var myInformationPopUpView:InformationPopUpView!
var myDatePopUpView:DatePopUpView!
var myPainDiagramView:PainDiagramPopUpView!
func notePopUpCalled() {
if (myPopupView != nil) {
self.myPopupView.view.removeFromSuperview()
}
// making different size pop up for different screens
var popUpWidth = GlobalConstants.ScreenStats.screenWidth - 60
if UIScreen.mainScreen().bounds.size.width > 320 {
if UIScreen.mainScreen().scale == 3 {
popUpWidth = 320
} else {
popUpWidth = 320
}
} else {
popUpWidth = 283
}
self.myPopupView = NotePopUpView(frame: CGRect(x: 20, y: 350, width: popUpWidth, height: 334))
myPopupView.center = self.view.center
self.view.addSubview(myPopupView)
showAnimate()
}
Generally when it is necessary to apply one method on different types recommended to use generics.
func setupView<T: UIView>(view: T) {
//Make all setups
}
Also you can use as? operator to downcasting your view types to UIView.
Hope it helps.
I'm working on an Accessibility project where I have a segmentedController in the NavigationBar. Almost everything is working fine until the focus comes at the middle (2/3) SegmentedController. It won't speak the the accessibilityLabel..
See my code.
I'm using NSNotifications to let the 'UIAccessibilityPostNotification' know when to focus:
func chatLijst() {
let subViews = customSC.subviews
let lijstView = subViews.last as UIView
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, lijstView)
}
func berichtenLijst() {
let subViews = customSC.subviews
let messageView = subViews[1] as UIView
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, messageView)
}
func contactenLijst() {
let subViews = customSC.subviews
let contactenView = subViews.first as UIView
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, contactenView)
}
func setupSegmentedController(){
let lijst:NSString = "Lijst"
lijst.isAccessibilityElement = false
lijst.accessibilityLabel = "Lijst met gesprekken"
let bericht:NSString = "Bericht"
bericht.isAccessibilityElement = false
bericht.accessibilityLabel = "Bericht schrijven"
let contacten:NSString = "Contacten"
contacten.isAccessibilityElement = false
contacten.accessibilityLabel = "Contacten opzoeken"
let midden:CGFloat = (self.view.frame.size.width - 233) / 2
customSC.frame = CGRectMake(midden, 7, 233, 30)
customSC.insertSegmentWithTitle(lijst, atIndex: 0, animated: true)
customSC.insertSegmentWithTitle(bericht, atIndex: 1, animated: true)
customSC.insertSegmentWithTitle(contacten, atIndex: 2, animated: true)
customSC.selectedSegmentIndex = 0
customSC.tintColor = UIColor.yellowColor()
customSC.isAccessibilityElement = true
self.navigationController?.navigationBar.addSubview(customSC)
}
Fix
Strange enough I had to restructure the subViews array in the setup func and replace UIAccessibilityPostNotification object with the new segmentsView array.
func chatLijst() {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, segmentsViews[0])
}
// Restructure subviews....
segmentsViews = [customSC.subviews[2], customSC.subviews[1], customSC.subviews[0]]
I'm using NSNotifications to let the 'UIAccessibilityPostNotification' know when to focus
Don't. That's a poor way to build a custom accessible control, and more importantly it can be confusing to the user. The screen changed notification doesn't just change focus, it also plays a specific sound that indicates to the user that the contents of the screen has changed.
Instead, I would recommend that you either make the subviews that you want appear as accessibility elements be accessibility elements with their own labels and traits and then rely on the OS to focus and activate them, or that you implement the UIAccessibilityContainer protocol in your custom control and then rely on the OS to focus and activate them.