SwiftUI to UIkit backBarButtonItem empty title creates a space - swiftui

In my Storyboard app I'm now starting to include SwiftUI views. I'm navigating from a SwiftUI view to an old UIkit VC and would like to ensure my navigation bar is the same as all other screen which, in this case, is a custom title and back button with the back arrow only, no text.
My storyboard VC viewDidLoad looks like this coming from a SwiftUI view:
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async { [self] in
self.navigationController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
let label1 = UILabel()
label1.text = "Manage Your Account"
label1.font = UIFont(name: "Raleway-Bold", size: 16)
label1.textColor = .white
label1.sizeToFit()
self.navigationController?.navigationBar.topItem?.titleView = label1
self.navigationController?.navigationBar.topItem?.rightBarButtonItems?.append(UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addAccountBtn(_:))))
}
//Required for table view
userAcctTableView.dataSource = self
userAcctTableView.delegate = self
}
My issue is the space created where the Back is suppose to be (yellow bloc) and because of this it pushes my title off centre (purple).

This was simply fixed by changing the order of the code lines:
let label1 = UILabel()
label1.text = "Manage Your Account"
label1.font = UIFont(name: "Raleway-Bold", size: 16)
label1.textColor = .white
label1.sizeToFit()
self.navigationController?.navigationBar.topItem?.titleView = label1
self.navigationController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
self.navigationController?.navigationBar.topItem?.rightBarButtonItems?.append(UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addAccountBtn(_:))))

Related

how to add % symbol at the end textField in swiftUI

I want to make a custom text field which will display the amount and (%) symbol
can anyone please tell me how can i acheive this.
if i enter 12 it should auto insert 12%
in UIKit it will be like textField.text = "(text) %"
struct UiTextFieldRepresentable: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> some UIView {
let textField = UITextField(frame: .zero)
textField.placeholder = "Enter your text"
textField.text = "\(text) %"
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
issue with this code is it is showing % sign before i start writing.
all i want want is when i start writing in the field it should postfix the % sign
TextField("", value: $input, format: .percent )
for this i made a function and on right side put a image as percentage that fixed my issue
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.tintColor = UIColor.clear
textField.rightView = makeImageForTextField()
textField.rightViewMode = .always
textField.keyboardType = .decimalPad
let toolBar = UIToolbar(
frame: CGRect(
x: 0, y: 0,
width: textField.frame.size.width,
height: 44
)
)
let doneButton = UIBarButtonItem(
title: "Done",
style: .done,
target: self,
action: #selector(textField.doneButtonTapped(button:))
)
toolBar.items = [doneButton]
toolBar.setItems([doneButton], animated: true)
textField.inputAccessoryView = toolBar
return textField
}
private func makeImageForTextField() -> UIButton {
let button = UIButton()
button.setImage(UIImage(systemName: "percent"), for: .normal)
button.tintColor = .black
button.isUserInteractionEnabled = false
return button
}
For the cases like this, please show your code. Because I'm not sure this is an actual question, maybe you haven't even started coding before asking it.
What exactly are you doing? To display the % symbol, just put in in the text (there're no formatting issues, it's not a special symbol etc):
struct ContentView: View {
#State var number: Int = 12
var body: some View {
Text("$ \(number) %")
.padding()
}
}
And you'll get your view rendered:

Make UIKit ContextMenu preview provider frame fit a SwiftUI view inside a UIHostingControler

I am learning Swift & SwiftUI as a hobby with no UIKit background, so I am not sure if this is currently possible. I would really like to use UIKit's context menus with SwiftUI (e.g. to implement submenus, action attributes and maybe custom preview providers).
My original idea was to create a LegacyContextMenuView with UIViewControllerRepresentable. Then I'd use a UIHostingController to add a SwiftUI View as a child of a UIViewController ContainerViewController to which I'd add a UIContextMenuInteraction.
My current solution kinda works but when the context menu is activated the preview frame of the 'ContainerViewController' view does not fit the size of UIHostingController view. I am not familiar with UIKit's layout system so I'd like to know:
Is it possible to add such constrains while the preview is activated?
Is it possible to preserve the clipShape of the underlying SwiftUI view inside the preview provider?
The code:
// MARK: - Describes a UIKit Context Menu
struct LegacyContextMenu {
let title: String
let actions: [UIAction]
var actionProvider: UIContextMenuActionProvider {
{ _ in
UIMenu(title: title, children: actions)
}
}
init(actions: [UIAction], title: String = "") {
self.actions = actions
self.title = title
}
}
// MARK: - A View that brings UIKit context menus into the SwiftUI world
struct LegacyContextMenuView<Content: View>: UIViewControllerRepresentable {
let content: Content
let menu: LegacyContextMenu
func makeUIViewController(context: Context) -> UIViewController {
let controller = ContainerViewController(rootView: content)
let menuInteraction = UIContextMenuInteraction(delegate: context.coordinator)
controller.view.addInteraction(menuInteraction)
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
func makeCoordinator() -> Coordinator { Coordinator(parent: self) }
class Coordinator: NSObject, UIContextMenuInteractionDelegate {
let parent: LegacyContextMenuView
init(parent: LegacyContextMenuView) { self.parent = parent }
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration?
{
// previewProvider nil = using the default UIViewController: ContainerViewController
UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: parent.menu.actionProvider)
}
}
class ContainerViewController: UIViewController {
let hostingController: UIHostingController<Content>
init(rootView: Content) {
self.hostingController = UIHostingController(rootView: rootView)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
setupHostingController()
setupContraints()
// Additional setup required?
}
func setupHostingController() {
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
}
// Not familiar with UIKit's layout system so unsure if this is the best approach
func setupContraints() {
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints([
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
hostingController.view.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
}
}
// MARK: - Simulate SwiftUI syntax
extension View {
func contextMenu(_ legacyContextMenu: LegacyContextMenu) -> some View {
self.modifier(LegacyContextViewModifier(menu: legacyContextMenu))
}
}
struct LegacyContextViewModifier: ViewModifier {
let menu: LegacyContextMenu
func body(content: Content) -> some View {
LegacyContextMenuView(content: content, menu: menu)
}
}
Then to test, I use this:
// MARK - A sample view with custom content shape and a dynamic frame
struct SampleView: View {
#State private var isLarge = false
let viewClipShape = RoundedRectangle(cornerRadius: 50.0)
var body: some View {
ZStack {
Color.blue
Text(isLarge ? "Large" : "Small")
.foregroundColor(.white)
.font(.largeTitle)
}
.onTapGesture { isLarge.toggle() }
.clipShape(viewClipShape)
.contentShape(viewClipShape)
.frame(height: isLarge ? 250 : 150)
.animation(.easeInOut, value: isLarge)
}
}
struct ContentView: View {
var body: some View {
SampleView()
.contextMenu(LegacyContextMenu(actions: [sampleAction], title: "My Menu"))
.padding(.horizontal)
}
let sampleAction = UIAction(
title: "Remove",
image: UIImage(systemName: "trash.fill"),
identifier: nil,
attributes: UIMenuElement.Attributes.destructive,
handler: { _ in print("Pressed 'Remove'") })
}
While long pressing, the context menu scaling animation respects the content shape of SampleView for both small and large sizes, but the preview pops out like this:
you must set preferredContentSize of ViewController to fit content size u want

Global alert in SwiftUI

I'm trying to present a global alert in SwiftUI. This alert should be displayed on top of everything regardless of what it is currently displayed / presented on screen (a sheet for example).
This is my code:
#main
struct MyApp: App {
#State private var showAlert = false
var body: some Scene {
WindowGroup {
MainView()
.onReceive(NotificationCenter.default.publisher(for:NSNotification.Name.SomeNotification), perform: { _ in
showAlert = true
})
.alert(
isPresented: $showAlert,
content: {Alert(title: Text("Alert!"))}
)
}
}
}
This in some cases will not work, for example if the notification is received when a sheet is currently presented on screen. In this case the alert is not displayed and the following message is displayed on the console:
Blockquote
[Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x7fbee6921400> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x7fbee642ac60> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x7fbee642ac60>) which is already presenting <TtGC7SwiftUI22SheetHostingControllerVS_7AnyView: 0x7fbee8405360>.
This make sense because I'm trying to present an alert on a view that is already presenting a sheet.
On UIKit I achieved this using the following class:
class GlobalAlertController: UIAlertController {
var globalPresentationWindow: UIWindow?
func presentGlobally(animated: Bool, completion: (() -> Void)?) {
globalPresentationWindow = UIWindow(frame: UIScreen.main.bounds)
globalPresentationWindow?.rootViewController = UIViewController()
globalPresentationWindow?.windowLevel = UIWindow.Level.alert + 1
globalPresentationWindow?.backgroundColor = .clear
globalPresentationWindow?.makeKeyAndVisible()
globalPresentationWindow?.rootViewController?.present(self, animated: animated, completion: completion)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
globalPresentationWindow?.isHidden = true
globalPresentationWindow = nil
}
}
This class allows me to display a global alert on top of everything in this way:
let alertController = GlobalAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Done", style: .cancel, handler: nil))
alertController.presentGlobally(animated: true, completion: nil)
Anyone know how to implement something like that in SwiftUI?
Just found that I can in fact use my old UIKit code to achieve this. The only thing that need to be changed is adding support for scenes (SwiftUI use scenes by design), like this:
class GlobalAlertController: UIAlertController {
var globalPresentationWindow: UIWindow?
func presentGlobally(animated: Bool, completion: (() -> Void)?) {
globalPresentationWindow = UIWindow(frame: UIScreen.main.bounds)
//This is needed when using scenes.
if let currentWindowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
globalPresentationWindow?.windowScene = currentWindowScene
}
globalPresentationWindow?.rootViewController = UIViewController()
globalPresentationWindow?.windowLevel = UIWindow.Level.alert + 1
globalPresentationWindow?.backgroundColor = .clear
globalPresentationWindow?.makeKeyAndVisible()
globalPresentationWindow?.rootViewController?.present(self, animated: animated, completion: completion)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
globalPresentationWindow?.isHidden = true
globalPresentationWindow = nil
}
}
Now I can just display the global alert like this:
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
MainView()
.onReceive(NotificationCenter.default.publisher(for:NSNotification.Name.SomeNotification), perform: { _ in
let alertController = GlobalAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Done", style: .cancel, handler: nil))
alertController.presentGlobally(animated: true, completion: nil)
})
}
}
}
It works, although a more SwiftUI like approach would be nice.

Set TextField and PickerView In UIAlertView

I want to create an alert with textfield and picker view.
I want that at top show picker view first and then textfield below picker view.
Here is my code
let alert = UIAlertController(title: "Create Meditation", message: "myMsg", preferredStyle: .alert)
alert.view.addSubview(pickerView)
alert.addTextField { (textField) in
textField.text = "Enter Message"
}
let action = UIAlertAction(title: "Send", style: .default) { (action) in
let textField = alert.textFields
print(textField)
}
let action2 = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(action)
alert.addAction(action2)
let height:NSLayoutConstraint = NSLayoutConstraint(item: alert.view, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: self.view.frame.height * 0.50)
alert.view.addConstraint(height);
self.present(alert, animated: true, completion: nil)
You should create a uiviewcontroller and give it this effect.
For example: In the next image, i created a view as modal. it is a uiviewcontroller with a gray view (In your case, in the gray view you should put the uitextfield and uipickerview). Then, i configure storyboard segue as present modally and to give the effect of the modal in the uiviewcontroller put:
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
You can use the following code :
let alertView = UIAlertController(
title: "Select",
message: "\n\n\n\n\n\n\n\n\n",
preferredStyle: .alert)
let pickerView = UIPickerView(frame:
CGRect(x: 0, y: 50, width: 260, height: 162))
pickerView.dataSource = self
pickerView.delegate = self
pickerView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
alertView.view.addSubview(pickerView)
alertView.addTextField(configurationHandler: configurationTextField)
alertView.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler:nil))
alertView.addAction(UIAlertAction(title: "Ok", style: .default, handler:{ (UIAlertAction) in
guard let text = self.textField?.text else {return}
print(text)
}))
present(alertView, animated: true, completion: {
pickerView.frame.size.width = alertView.view.frame.size.width
})
You can't use UIAlertController for that, as it doesn't have an option to add views other than textfields.
So, you have to write your own control, that will mimic UIAlertController.
You can write view controller and present it modally, or create a view and add it to the controller view.
Also, could be good idea to try to find ready component on github.com or cocoacontrols.com and modify it as you need it.
try these Pods hope it helps you
https://github.com/nealyoung/NYAlertViewController

How to add spacing to Left BarButtonItem Image with Swift 3

I would like to add space to the left of the bar button.
So, I add this code.
navbar.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin, .flexibleRightMargin]
navbar.delegate = self
UINavigationBar.appearance().barTintColor = UIColor(red: 0.0/255.0, green:49.0/255.0, blue:79.0/255.0, alpha:0.1)
UINavigationBar.appearance().tintColor = UIColor.white
UINavigationBar.appearance().isTranslucent = true
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white]
navItem.title = prefs.value(forKey: "PROVIDER_NAME") as! String?
let image = UIImage(named: "back_image")
navItem.leftBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(addTapped))
After adding this code, the button image does not show well and looks no good.
Could anyone help me to solve this problem?
Hi Code given below might be useful for you.
extension UIBarButtonItem {
class func itemWith(colorfulImage: UIImage?, target: AnyObject, action: Selector) -> UIBarButtonItem {
let button = UIButton(type: .custom)
button.setImage(colorfulImage, for: .normal)
button.frame = CGRect(x: 0.0, y: 0.0, width: 44.0, height: 44.0)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -50, 0, 0)
button.addTarget(target, action: action, for: .touchUpInside)
let barButtonItem = UIBarButtonItem(customView: button)
return barButtonItem
}
}
You can increase or decrease Left Padding for UIEdgeInsetsMake(0, -50, 0, 0) as per your requirement for left padding.
You can use above extension in your code as describe below.
self.navigationItem.leftBarButtonItem = UIBarButtonItem.itemWith(colorfulImage: UIImage(named: "ic_back")?.withColor(UIColor.white), target: self, action: #selector(btnBackClicked))
func btnBackClicked() {
print("your code here on back button tapped.")
}