Drawing a pie isn't difficult if I do it with Path.
import SwiftUI
struct ContentView8: View {
var body: some View {
PieSlice(start: .degrees(-90), end: .degrees(120))
.fill(.pink)
}
}
struct PieSlice: Shape {
let start: Angle
let end: Angle
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
path.move(to: center)
path.addArc(center: center, radius: rect.midX, startAngle: start, endAngle: end, clockwise: false)
return path
}
}
Actually, I want to animate this pie such that it will gradually deploy starting at -90 degrees. In the code above, I suppose I cannot animate the pie because the PieSlice guy isn't a View. Or can I? If I can't, is there an alternative way of drawing a pie so that I can animate it?
Thanks a million.
Señor Tomato Source
Hostage Negotiator at
Tomato Source Association of North America
Post
Replies
Boosts
Views
Activity
I've been trying to save a selected color with UserDefaults from UIColorPickerViewController. But I run into a color space fiasco. Anyway, here come my lines of code.
class ViewController: UIViewController, UIColorPickerViewControllerDelegate {
@IBOutlet weak var imageView: UIImageView!
@IBAction func selectTapped(_ sender: UIButton) {
let picker = UIColorPickerViewController()
picker.delegate = self
picker.selectedColor = .yellow
picker.supportsAlpha = false
present(picker, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let color = UserDefaultsUIColor.shared.readColor(key: "MyColor") {
print("Color being read: \(color)")
}
}
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
let color = viewController.selectedColor
print("Selected color: \(color)")
UserDefaultsUIColor.shared.saveColor(color: viewController.selectedColor, key: "MyColor")
}
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
imageView.backgroundColor = viewController.selectedColor
}
}
class UserDefaultsUIColor {
static let shared = UserDefaultsUIColor()
func saveColor(color: UIColor, key: String) {
let userDefaults = UserDefaults.standard
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false) as NSData?
userDefaults.set(data, forKey: key)
} catch {
print("Error UserDefaults: \(error.localizedDescription)")
}
}
func readColor(key: String) -> UIColor? {
let userDefaults = UserDefaults.standard
if let data = userDefaults.data(forKey: key) {
do {
if let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) {
return color
}
} catch {
print("Error UserDefaults")
}
}
return nil
}
}
I first start out with a yellow color (UIColor.yellow). And I select a color whose RGB values are 76, 212, 158, respectively. And the color picker guy returns the following.
kCGColorSpaceModelRGB 0.298039 0.831373 0.619608 1
And I get the following in reading the saved color data object.
UIExtendedSRGBColorSpace -0.270778 0.84506 0.603229 1
How can I save and read color data objects consistently? I could specify a color space when I save a color. But it doesn't go well.
Muchos thankos
Señor Tomato de Source
I am just playing with NSTextList by creating a sample iOS app. The following is my code.
import UIKit
class ViewController: UIViewController {
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = ""
textView.contentInsetAdjustmentBehavior = .automatic
textView.backgroundColor = .white
textView.font = UIFont.systemFont(ofSize: 20.0)
textView.textColor = .black
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.widthAnchor.constraint(equalToConstant: 600.0),
textView.heightAnchor.constraint(equalToConstant: 600.0)
])
return textView
}()
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("End list", for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitleColor(.lightGray, for: .highlighted)
button.backgroundColor = .black
button.layer.cornerRadius = 8.0
button.addTarget(self, action: #selector(fixTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 100.0),
button.heightAnchor.constraint(equalToConstant: 42.0)
])
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(textView)
view.addSubview(button)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tapGesture)
NSLayoutConstraint.activate([
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20.0)
])
let list = NSTextList(markerFormat: .diamond, options: 0)
list.startingItemNumber = 1
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.textLists = [list]
let attributes = [NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24.0)]
let attributedStr = NSMutableAttributedString(string: "\n\n\n\n\n", attributes: attributes)
textView.textStorage.setAttributedString(attributedStr)
}
@objc func fixTapped() {
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
}
When the app launches itself, I get 5 lines of diamond guys as shown in the following screenshot.
If I keep pressing the delete key with a connected keyboard, the list will be gone as shown below.
But if I press the RETURN key several times, the diamond list will come back as shown below.
So how can I end this June TextList madness? In code, I have the dismissKeyboard function if I can end this madness programmatically.
Thanks,
Señor Tomato Spaghetti
Chief Janitor at
Southeastern Tomato Spaghetti Trade Association
I have a simple app that implements a custom URL scheme. When I enter the scheme for my app in Safari, the app will launch itself. So far, so good... Now, when I initiate the app switcher, I have my app and the web browser (Safari). Is there a way of not showing them in the app switcher? Or can I at least stop the web browser from appearing in the app switcher? Is it even possible for me to terminate Safari programmatically from my app, provided that that is not going to violate the app store guidelines? Thanks.
I'm using the custom URL scheme to open my app through Safari. When the app appears, the status bar shows 'Safari' at the top-left corner. Is there a way of stopping the app from showing Safari's name? I don't want the user to tap the name and go back to Safari. There is nothing special in my SceneDelegate. So I wonder if it's probably the matter of settings in the Settings app? Thanks.
I didn't know that Settings Bundle exists till two days ago. Anyway, I've tested it with a simple example. As shown in the screenshot below, I have one group, one text field, one slider and two toggle buttons.
I am able to read the values from all of them except the slider. I wonder if it's a bug? I'm using Xcode 14.2. In the following code, the app won't go inside the if clause for the PSSliderSpecifier key.
import UIKit
class ViewController: UIViewController {
// MARK: - Life cyle
override func viewDidLoad() {
super.viewDidLoad()
let defaultValues = [String: AnyObject]()
UserDefaults.standard.register(defaults: defaultValues)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
/* settings */
fetchSettingBundleData()
}
@objc func fetchSettingBundleData() {
let userDefaults = UserDefaults.standard
if let settingsURL = Bundle.main.url(forResource: "Root", withExtension: "plist", subdirectory: "Settings.bundle"), let settings = NSDictionary(contentsOf: settingsURL), let preferences = settings["PreferenceSpecifiers"] as? [NSDictionary] {
var defaultsToRegister = [String: Any]()
for preferenceSpecification in preferences {
if let key = preferenceSpecification["Type"] as? String, let value = preferenceSpecification["Title"] {
defaultsToRegister[key] = value
}
}
userDefaults.register(defaults: defaultsToRegister)
}
if let groupName = userDefaults.string(forKey: "PSGroupSpecifier") {
print("Group name: \(groupName)")
}
if let _ = userDefaults.string(forKey: "PSTextFieldSpecifier") {
if let text = userDefaults.string(forKey: "name_preference") {
print("Textfield \(text)")
}
}
if let _ = userDefaults.string(forKey: "PSToggleSwitchSpecifier") {
if let value = userDefaults.string(forKey: "enabled_preference1") {
print("Toggle \(value)") // 0 or 1
}
}
if let _ = userDefaults.string(forKey: "PSToggleSwitchSpecifier") {
if let value = userDefaults.string(forKey: "enabled_preference2") {
print("Toggle2 \(value)") // 0 or 1
}
}
if let _ = userDefaults.string(forKey: "PSSliderSpecifier") {
print("heck....") // No show
if let value = userDefaults.string(forKey: "slider_preference") {
print("Slider \(value)")
}
}
}
}
How do you save a picture from the capture-screen with AVCaptureSession to Photo? My capture-screen looks like a square as show below.
Yet I've ended up with an 1080 × 1920 image as shown below. I have an iPhone XR, and I always end up with 1080 × 1920 images. How come the aspect ration never changes?
My code has the following lines
import UIKit
import AVFoundation
class CaptureViewController: UIViewController, AVCapturePhotoCaptureDelegate {
var captureSession: AVCaptureSession!
var cameraDevices: AVCaptureDevice!
var imagePhotoOutput: AVCapturePhotoOutput!
enum CameraCase {
case front
case back
}
// MARK: - IBAction
@IBAction func takePictureTapped(_ sender: UIButton) {
snapPicture()
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
prepareCamera(cameraCase: .back)
}
// MARK: - Camera
func prepareCamera(cameraCase: CameraCase) {
/* removing existing layers */
if let sublayers = self.view.layer.sublayers {
for sublayer in sublayers {
if sublayer.isKind(of: AVCaptureVideoPreviewLayer.self) {
sublayer.removeFromSuperlayer()
}
}
}
/* creating a capture session */
captureSession = AVCaptureSession()
guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: cameraCase == .front ? .front : .back).devices.first else { return }
let videoInput = try? AVCaptureDeviceInput(device: device)
if captureSession.canAddInput(videoInput!) {
captureSession.addInput(videoInput!)
imagePhotoOutput = AVCapturePhotoOutput() // setting output destination
captureSession.addOutput(imagePhotoOutput) // adding photo output to session
}
/* creating a capture layer */
let previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: captureSession)
previewLayer.frame = CGRect(x: 0.0, y: 200.0, width: view.frame.width, height: view.frame.height - 500.0)
previewLayer.videoGravity = AVLayerVideoGravity.resize
/* adding video capture layer to the view layer */
self.view.layer.addSublayer(previewLayer)
/* starting capture session */
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
}
func snapPicture() {
let settingsForMonitoring = AVCapturePhotoSettings()
imagePhotoOutput?.capturePhoto(with: settingsForMonitoring, delegate: self as AVCapturePhotoCaptureDelegate)
}
// MARK: - Delegate methods
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if error == nil {
guard let imageData = photo.fileDataRepresentation() else {
print("Error while generating image from photo capture data.");
return
}
if let image = UIImage(data: imageData) {
saveImage(image)
}
}
}
// MARK: - Saving an image to Photo Library
func saveImage(_ image: UIImage) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError: contextInfo:)), nil)
}
@objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let error = error {
print("An error has occurred: \(error.localizedDescription)")
} else {
print("Saved...")
}
}
}
Thanks.
I have revisited AVCaptureSession in UIKit to capture a snapshot with the FaceTime camera. And my sample app will crash when AVCaptureSession starts running. Does anyone know how to fix it? The console says the following purple warning.
-[AVCaptureSession startRunning] should be called from background thread. Calling it on the main thread can lead to UI unresponsiveness
import UIKit
import AVFoundation
class CaptureViewController: UIViewController, AVCapturePhotoCaptureDelegate {
var captureSession: AVCaptureSession!
var cameraDevices: AVCaptureDevice!
var imagePhotoOutput: AVCapturePhotoOutput!
enum CameraCase {
case front
case back
}
// MARK: - IBAction
@IBAction func selectTapped(_ sender: UIButton) {
snapPicture()
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
prepareCamera(cameraCase: .front)
}
// MARK: - Camera
func prepareCamera(cameraCase: CameraCase) {
/* removing existing layers */
if let sublayers = self.view.layer.sublayers {
for sublayer in sublayers {
if sublayer.isKind(of: AVCaptureVideoPreviewLayer.self) {
sublayer.removeFromSuperlayer()
}
}
}
/* creating a capture session */
captureSession = AVCaptureSession()
if cameraCase == .front {
guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.first else { return }
let videoInput = try? AVCaptureDeviceInput(device: device)
if captureSession.canAddInput(videoInput!) {
captureSession.addInput(videoInput!)
imagePhotoOutput = AVCapturePhotoOutput() // setting output destination
captureSession.addOutput(imagePhotoOutput) // adding photo output to session
}
} else {
guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .front).devices.first else { return }
let videoInput = try? AVCaptureDeviceInput(device: device)
if captureSession.canAddInput(videoInput!) {
captureSession.addInput(videoInput!)
imagePhotoOutput = AVCapturePhotoOutput() // setting output destination
captureSession.addOutput(imagePhotoOutput) // adding photo output to session
}
}
/* creating a capture layer */
let captureVideoLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: captureSession)
captureVideoLayer.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
captureVideoLayer.videoGravity = AVLayerVideoGravity.resizeAspect
/* adding video capture layer to the view layer */
self.view.layer.addSublayer(captureVideoLayer)
/* starting capture session */
captureSession.startRunning() //<<<<<<<<<<<<<<<<<<<<<<<<<<< The console shows a purple warning here.
}
}
I have downloaded the UniversalMac_13.0_22A379_Restore.ipsw file. How do we use this file to install the new OS? Thanks.
I have an array of dictionaries. And I need to sort this array in an ascending order.
someArray = someArray.sorted { lValue, rValue in lValue.categoryName < rValue.categoryName }
So one of the dictionaries is categoryName. Now, I have a case where the category name of an array element is something specific, say 'price,' then that element must be listed at the end as an exception to the rule above. Is that possible? Thanks.
I have created a very simple sample project just to make my point using RealmSwift..
// ContentView.swift //
import SwiftUI
import RealmSwift
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
NavigationView {
VStack {
Spacer()
NavigationLink("Listing all meals") {
ListView()
.environmentObject(viewModel)
}
Spacer()
}
}
}
}
// ListView.swift //
import SwiftUI
import RealmSwift
struct ListView: View {
@EnvironmentObject var viewModel: ViewModel
@State var meals = [MealDB]()
var body: some View {
List {
ForEach(meals) { meal in
HStack {
Text("\(meal.title)")
.padding(.leading, 6.0)
Spacer()
Button {
viewModel.model.delete(id: meal.id)
} label: {
Text("Delete")
}
.padding(.trailing, 6.0)
.buttonStyle(.borderless)
}
.onDrag {
return NSItemProvider()
}
}
.onMove(perform: move(from:to:))
}
.onAppear {
updateData()
}
}
func updateData() {
meals.removeAll()
// data from Realm database
for mealItem in viewModel.mealItems {// <<<<<<<<<<
meals.append(mealItem)
}
meals.sort {
(($0).place < (($1).place))
}
}
}
// ViewModel.swift //
import Foundation
import RealmSwift
class ViewModel: ObservableObject {
@Published var model = MealStore()
var mealItems: Results<MealDB> {
model.items
}
}
final class MealStore: ObservableObject {
var config: Realm.Configuration
init() {
config = Realm.Configuration()
}
var realm: Realm {
return try! Realm(configuration: config)
}
var items: Results<MealDB> {
realm.objects(MealDB.self)
}
}
// MealDB.swift //
import Foundation
import RealmSwift
class MealDB: Object, Identifiable {
@objc dynamic var id = ""
@objc dynamic var title = ""
@objc dynamic var order = 0
@objc dynamic var place = 0
override class func primaryKey() -> String? {
"id"
}
}
ListView has a list of meals. Each row comes with a button that lets me delete the corresponding row. And the app will crash inside the updateData function. I have found out that the issue is the way how SwiftUI works and hangs on to the old set of data even after I tap the delete button. So a solution is to 'freeze up' the dataset. And the app won't crash when I tap the delete button.
for mealItem in viewModel.mealItems.freeze() {
...
}
Now, my question is... Are there reasons for not freezing up the dataset? If there is no downside, how come MongoDB just doesn't tell us to do it when we use access a dataset in Realm? Thanks.
In UIKit, it's just the matter of setting table view's allowsSelection to false if you don't want to allow selection. If each row has a button, it can still be clicked on. Can we do that in SwiftUI?
I have the following simple SwiftUI project.
import SwiftUI
struct ContentView: View {
@State private var fruits = ["Apple", "Banana", "Mango", "Watermelon", "Pineapple", "Strawberry", "Orange"]
@State private var isEditable = true
var body: some View {
List {
ForEach(fruits, id: \.self) { fruit in
HStack {
Text(fruit)
.onDrag {
return NSItemProvider()
}
Spacer()
Button {
print("Hello")
} label: {
Text("Tap me")
}
}
}
.onMove(perform: move)
}
.onTapGesture(perform: {
return
})
.listStyle(.plain)
}
func move(from source: IndexSet, to destination: Int) {
fruits.move(fromOffsets: source, toOffset: destination)
withAnimation {
isEditable = false
}
}
}
The tap gesture prevents row interaction. So I won't even be able to tap the button. How can I disable row selection while allowing interaction inside the list row?
List {
}
.onAppear {
UITableView.appearance().allowsSelection = false
UITableViewCell.appearance().selectionStyle = .none
}
The lines of code above don't work, either.
Thanks.
I have downloaded a sample project at raywenderlich.com (https://www.raywenderlich.com/22408716-drag-and-drop-editable-lists-tutorial-for-swiftui). I am working on a project involving DropDelegate. And I have a question with this project to make my point.
In reference to the picture shown below, if I grab, drag and move Count Sheep, its preview picture will shrink. How could I prevent the preview picture from shrinking its size?
struct ContentView: View {
@EnvironmentObject private var todoList: TodoList
@State private var isShowingAddTodoView = false
@State private var editMode: EditMode = .inactive
@State private var focusId: Int?
func addTodo() {
isShowingAddTodoView = true
}
var body: some View {
NavigationView {
VStack {
FocusTodoView(focusId: focusId)
.padding()
.onDrop(
of: [TodoItem.typeIdentifier],
delegate: TodoDropDelegate(focusId: $focusId))
ScrollView {
ActiveTodoView()
CompletedTodoView()
.disabled(editMode.isEditing)
.onDrop(of: [TodoItem.typeIdentifier], isTargeted: nil) { itemProviders in
for itemProvider in itemProviders {
itemProvider.loadObject(ofClass: TodoItem.self) { todoItem, _ in
guard let todoItem = todoItem as? TodoItem else { return }
DispatchQueue.main.async {
todoList.updateTodo(withId: todoItem.id, isCompleted: true)
}
}
}
return true
}
}
.applyPlainListAppearance()
.navigationBarTitle("Drag Todo")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
EditButton()
Button(action: addTodo) {
Image(systemName: "plus")
}
.disabled(editMode.isEditing)
}
}
.environment(\.editMode, $editMode)
.sheet(isPresented: $isShowingAddTodoView) {
AddTodoView()
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
I wish I had a simpler sample. That's the only sample I have been able to find. Anyway, I've been asking Google all day about "SwiftUI DropDelegate preview" with no luck. Thanks.
I have a simple case as follows.
class Monster: ObservableObject {
static let shared = Monster()
@Published var selectionChanged = false
}
struct ContentView: View {
@ObservedObject var monster = Monster.shared
@State private var isOn = false
var body: some View {
VStack {
Button {
monster.selectionChanged.toggle()
} label: {
Text("Tap me")
}
.padding(.vertical, 60.0)
SecondView()
}
}
}
struct SecondView: View {
@StateObject var monster = Monster.shared
var body: some View {
VStack {
Text("Hello")
}.onChange(of: monster.selectionChanged) { _ in
print("GGGG")
}
}
}
So SecondView receives a call from Monster with through onChange.. Is there a simpler approach where SecondView receives a call without it? Thanks.
I often use security-scoped bookmarks when I develop a desktop application in Cocoa. This time, I need to use them in an iOS app, using SwiftUI framework.
I don't quite remember the history, but I use UIDocumentPickerViewController through UIViewControllerRepresentable to let the user select a file. And I have a model where I save file name, file path, its bookmark (Data) with NSKeyedArchiver.. And everything goes well when I run the app in a simulator.
Yet, FileManager says each file in the model does not exist. One of the path is something like the following.
/private/var/mobile/Containers/Shared/AppGroup/749F05F0-12BC-40AC-B5C4-72571145C624/File Provider Storage/Test/somefile.txt
Since it doesn't exist, I cannot even resolve it. How can I resolve the bookmark if a file ends up at the File Provider Storage folder? Do I need a special capability that I don't know about or something? Thanks.